本页属于关于时序的系列文章之一。前几页介绍了一些基础主题:时序计算背后的理论,以及时钟周期约束。我们也展示并讲解了几份时序报告。现在来谈一谈这些知识的一个重要用途:解决时序问题。
引言
FPGA 工具最艰难的任务,就是满足时序约束(timing constraints)的要求。多数情况下工具能够做到,但有时也会失败。当失败发生时,找出失败原因并加以解决的责任就落在我们这些工程师身上。这个任务有一个名字:我们称之为时序收敛(timing closure)。这可不是一件容易的事。
时序收敛为什么难?关键在于,工具中运行的是一套布局布线(place and route)算法,它试图以最优方式使用 FPGA 的资源。通常,这个算法一开始并不会花太大力气去摆放 FPGA 上的逻辑元件,而是很快进入迭代过程:工具遍历所有路径(path),找出未满足时序约束的路径,并对这些路径采取修正措施。最主要的修正手段,是把逻辑元件移动到 FPGA 上的不同位置,并调整布线。至于更高级的修正方法,每个 FPGA 工具各有各的手段。
当所有路径都满足时序约束时,实现就算完成了。但实现也可能以另一种方式结束:工具未能达到目标,最终放弃了尝试。这种情况下,我们得到的是工具在停止努力之前所能达到的结果。这个结果不一定是最优的:某些路径本来还可以继续改进,但工具当时正忙着修复别的地方;而修复失败后,工具便放弃了,没有再尝试去修其他问题。就好像工具在说:“如果一个实现反正都会失败,再花时间修它也没有意义。”
我们作为 FPGA 设计师的任务,就是审视这个次优的结果,找出时序约束的目标为什么没能达成。
这类算法会随着时间不断改进。当某种失败原因变得常见时,软件的下一版本通常就会针对那种情况给出专门的解决方案。因此,当工具失败时,通常都有一个确实的、值得寻找的原因。
所以我们要看看工具到底做到了什么,然后问自己:工具为什么失败?是不是我们要求了一件不可能的事?更重要的是,我们是不是要求了一件不必要的事?也许那个让工具失败的路障,根本就是我们不需要的东西。又或许,是优化算法本身没有发挥好?有时候纯粹是运气不好:初始布局可能差到让后续的改进尝试注定失败。
无论问题是什么,找出失败原因的过程都像侦探勘察犯罪现场:事实就摆在我们面前,但原因往往隐藏在表面之下。这些事实大多能在时序报告里找到,但线索不会自己主动暴露出来。你必须经常问自己:这份时序报告中,哪些地方是错误、异常或不正常的?就像侦探寻找罪犯一样,你的目标是找到一个能引出问题根源的细节。
但要想发现什么是异常,你必须先知道什么是正常。例如,一个具有特定扇出(fan-out)的信号网络(net)的正常延迟是多少?实现某个逻辑功能时,正常的逻辑级数是多少?这些问题的答案因 FPGA 而异。因此,即使在一切正常的时候,也必须通过阅读和理解时序报告来积累经验。你必须知道一份一切正常的时序报告长什么样,才能发现时序报告中暗示问题的地方。如果你好奇我为什么前几页要钻到那么多细节里,这就是原因之一。
关键路径
当工具无法满足时序约束时,就意味着至少有一条路径的时序余量(slack)为负。其中时序余量最负的那条路径称为关键路径(critical path)。这个名字本身就反映了时序收敛的常见策略:把注意力集中在关键路径上,往往是解决时序问题的办法。但我下面会说明,这个策略有时也可能让你白费功夫。
当时序约束得到满足时,关键路径就是时序余量最小的那条路径。这条路径通常并不值得特别关注,因为工具不会试图去改进那些时序余量为正的路径。所以,如果最差的那条路径时序余量为正,那么它成为“最差”可能只是一个巧合。
但如果时序余量为正、且几乎接近于零(比如小于 0.2 ns),这可能说明,让这条路径满足时序约束并不容易。这类关键路径可以视为一种警告:它们将来可能会带来麻烦(尤其是当 FPGA 后来被更多的逻辑填满、工具的优化精力被分散到其他路径时)。
时序报告通常为每个时钟列出有限数量的关键路径。大多数 FPGA 工具的默认设置是:即使时序余量为正(也就是时序约束已满足),也照样显示几条关键路径。这个设置是值得推荐的。
一个关键路径的例子
我先从一个关键路径的分析示例讲起。这个示例的 Verilog 代码如下:
reg [24:0] calc, result;
reg [11:0] x, y, z;
always @(posedge clk)
begin
calc <= x * y + z;
result <= calc;
end
在这个示例中,@clk 的频率是 250 MHz,而且没有用 PLL 来产生这个时钟。另外,假设 @x、@y 和 @z 都是与 @clk 同步的寄存器。给这些寄存器赋值的 Verilog 代码没有显示,因为它与讨论无关。
用 Vivado 跑这段代码时,时序约束没有被满足。时序报告中的关键路径如下:
Slack (VIOLATED) : -0.239ns (required time - arrival time) Source: x_reg[1]__0_replica_2/C (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg[23]/D (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 4.180ns (logic 1.642ns (39.282%) route 2.538ns (60.718%)) Logic Levels: 7 (CARRY8=4 LUT3=1 LUT4=1 LUT6=1) Clock Path Skew: -0.087ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.176ns = ( 7.176 - 4.000 ) Source Clock Delay (SCD): 3.864ns Clock Pessimism Removal (CPR): 0.601ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.032ns (routing 0.396ns, distribution 1.636ns) Clock Net Delay (Destination): 1.748ns (routing 0.365ns, distribution 1.383ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y0 (CLOCK_ROOT) net (fo=106, routed) 2.032 3.864 clk_IBUF_BUFG SLICE_X54Y54 FDRE r x_reg[1]__0_replica_2/C ------------------------------------------------------------------- ------------------- SLICE_X54Y54 FDRE (Prop_HFF2_SLICEL_C_Q) 0.137 4.001 r x_reg[1]__0_replica_2/Q net (fo=21, routed) 0.371 4.372 x[1]_repN_2 SLICE_X56Y53 LUT6 (Prop_E6LUT_SLICEL_I1_O) 0.219 4.591 r calc[23]_i_101/O net (fo=2, routed) 0.550 5.141 calc[23]_i_101_n_0 SLICE_X54Y57 CARRY8 (Prop_CARRY8_SLICEL_DI[5]_CO[7]) 0.228 5.369 r calc_reg[23]_i_30/CO[7] net (fo=1, routed) 0.030 5.399 calc_reg[23]_i_30_n_0 SLICE_X54Y58 CARRY8 (Prop_CARRY8_SLICEL_CI_O[1]) 0.163 5.562 r calc_reg[23]_i_22/O[1] net (fo=3, routed) 0.351 5.913 calc_reg[23]_i_22_n_14 SLICE_X56Y57 LUT3 (Prop_C6LUT_SLICEL_I1_O) 0.146 6.059 r calc[23]_i_26/O net (fo=3, routed) 0.240 6.299 calc[23]_i_26_n_0 SLICE_X55Y58 LUT4 (Prop_A6LUT_SLICEM_I0_O) 0.089 6.388 r calc[23]_i_7/O net (fo=1, routed) 0.407 6.795 calc[23]_i_7_n_0 SLICE_X53Y57 CARRY8 (Prop_CARRY8_SLICEM_DI[2]_O[4]) 0.308 7.103 r calc_reg[23]_i_2/O[4] net (fo=1, routed) 0.538 7.641 P[20] SLICE_X54Y56 CARRY8 (Prop_CARRY8_SLICEL_S[4]_O[7]) 0.352 7.993 r calc_reg[23]_i_1/O[7] net (fo=1, routed) 0.051 8.044 P0_out[23] SLICE_X54Y56 FDRE r calc_reg[23]/D ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y0 (CLOCK_ROOT) net (fo=106, routed) 1.748 7.176 clk_IBUF_BUFG SLICE_X54Y56 FDRE r calc_reg[23]/C clock pessimism 0.601 7.777 clock uncertainty -0.035 7.741 SLICE_X54Y56 FDRE (Setup_HFF_SLICEL_C_D) 0.063 7.804 calc_reg[23] ------------------------------------------------------------------- required time 7.804 arrival time -8.044 ------------------------------------------------------------------- slack -0.239
这条路径的时序余量为 -0.239 ns,所以它只是轻微违反了时序约束。首先要看的是这条路径的起点和终点:看报告头部里的 Source 和 Destination,可以看到 x_reg 和 calc_reg。所以问题的来源显然在这段代码上:
calc <= x * y + z;
这并不令人意外,因为它是这段 Verilog 里唯一有实际意义的运算。在真实场景中,导致问题的逻辑部分往往不会这么明显。
时序报告还清楚地表明,逻辑级数为 7:这条组合逻辑路径(combinatorial path)太长了。换句话说,在 @clk 的两个时钟边沿之间要完成的事情太多。
但失败的实际原因是什么?是不是因为布线延迟占了总延迟的 61%?回想一下,通常的经验法则是布线延迟大约占总延迟的 40%。那么,能不能想办法让 FPGA 工具做得更好?这种尝试多半不会成功,因为工具在放弃某条路径的时序约束之前,通常已经花了很大力气。
试图去改变 @x 和 @calc 之间的逻辑功能同样是徒劳的:乘法是必不可少的,没有什么更简单的东西可以替换它。
我会在下一页介绍一些解决这类问题的可行方法。但对本例来说,罗列一堆技巧并不会有帮助。这个简单的例子说明,有时候我们需要像侦探一样思考。
没有比动脑更好的办法
读时序报告时,第一个要问的问题是:它有什么异常?在本例中,答案是:这条组合路径完全由切片(slice)构成。几乎所有 FPGA 都有专门的算术单元(不同厂商叫法不一,例如 DSP、ALU),当设计里出现乘法运算时通常会用到它们。事实上,乘加运算正是这类专用逻辑中最常见的功能之一。所以在大多数情况下,最简单的解决办法就是让工具使用专用算术单元。这个方案对应的时序报告在本页末尾给出。
但我们真正应该问的是:为什么这里用了切片,而不是专用算术单元?最常见的原因是,FPGA 上所有的专用算术单元已经被设计中的其他部分用光了。这种情况下,需要做的修改可能和关键路径完全无关:也许需要从设计里砍掉一些逻辑,以便腾出几个算术单元;也可能需要指示工具在设计的各个部分之间重新分配算术单元的使用。
在本例中,使用切片而不是算术单元,是因为我就想让它这样:我故意关闭了算术单元的使用(通过 Vivado 综合器(synthesizer)的一个参数,把 max_dsp 设成了 0)。但这并不表示这个例子是人为捏造的。有时候人们确实会给 FPGA 工具传入错误的参数,结果恰好就导致这种情况。实际上,有时刻意不用算术单元反而是对的,因为设计里其他地方更需要它们。
所以,最简单的解决方案是使用专用算术单元。但如果我们必须用切片呢?答案仍然是间接的。回想一下,出问题的这段代码是:
calc <= x * y + z;
但请注意紧接着还有这一句:
result <= calc;
如果 @calc 只在这一行被使用、别处不再引用,那我们就可以把计算拆成两个阶段。这种技术通常称为流水线(pipeline)处理。于是 Verilog 代码变成这样:
reg [24:0] calc, result;
reg [11:0] x, y, z, z_d;
always @(posedge clk)
begin
z_d <= z;
calc <= x * y;
result <= calc + z_d;
end
在这个方案中,@calc 只被赋予乘法的结果。@z 的值要到下一个时钟周期、在做加法时才会用到。更准确地说,加法运算发生在 @calc 与 @z_d 之间,因为这个运算晚了一个时钟周期。所以 @result 的最终值和原来完全一样。
这样修改之所以容易,是因为在原来的 Verilog 代码里,@result 只是 @calc 的延迟拷贝。在实际项目中,我们通常没有这么好的运气。
另外注意,关键路径只涉及 @calc 和 @x,@z 甚至没有出现在这条路径里。所以这样做的目的,是减轻算术运算的负担;更准确地说,是减少逻辑级数。
回想一下,关键路径是优化算法运行完之后最差的那条路径。这个算法不会去问问题出在哪里,它只会设法改进那些时序余量为负的路径。因此,即使本例的解决方案需要对 @z 动手,与 @z 相关的路径却不是关键路径。这只是一个巧合,但这种巧合经常发生。
采用该方案后,新的关键路径的时序报告也放在本页末尾。它显示逻辑级数从 7 降到了 6,数据路径延迟因此减少了 0.715 ns,足以满足时序约束并留有余量。
从这个例子得到的经验是:关键路径并不总是问题的直接根源。问“这条路径为什么失败”仍然是正确的,但解决方案可能藏在别处。另外要记住,每种 FPGA 工具都有一些实用工具,它们提供的信息能帮助你找到问题的根本原因。花时间去了解这些工具、读一读它们的文档,是值得的。
尽早避免问题,胜过事后补救
如果从一开始就把逻辑设计做对,那么时序收敛中的很多工作是可以避免的。这要求你时刻意识到:逻辑设计不是软件。Verilog 代码的目的不是在仿真中得到正确结果,真正重要的是综合器(synthesizer)根据 Verilog 代码生成出来的硬件结构。
一个好的逻辑设计,首先要思考清楚:这段逻辑应该以什么方式最好地实现它的目的。这个思考过程也包括识别与时序相关的潜在障碍。
经验较少的 FPGA 设计师常常通过试错来写 Verilog 代码:先用仿真看看逻辑是否符合预期,然后一点一点修改,直到仿真输出正确为止。这样得到的结果,可能是根本无法在硬件上使用的逻辑:为了满足时序约束,Verilog 代码必须彻底重写。
提前考虑 Verilog 代码会产生哪些组合逻辑路径,这很重要。具体方法是:看每一个寄存器,沿着它出发的组合逻辑路径一路走到终点。回想一下,组合逻辑路径总是从一个寄存器开始、到另一个寄存器结束。
我们来看这个例子:
reg [15:0] a, b;
wire [16:0] x, y;
reg [33:0] z;
assign x = a + 2;
assign y = b + 3;
always @(posedge clk)
z <= x * y;
以 @a 为例:当这个寄存器改变时,组合逻辑路径首先到达 @x。但 @x 不是寄存器,它是通过连续赋值语句更新的,所以路径会继续延伸到 @z。也就是说,这条组合逻辑路径里完成了两个重要运算:一个加法和一个乘法。这算不算太多?需不需要通过流水线(pipeline)把它拆成两个时钟周期?这取决于时钟频率以及使用的是哪款 FPGA。
另一个重要因素是:把组合逻辑路径缩短有多难。有时候,长组合路径是不可避免的。但如果某条路径的时序很容易改善,那就去改它,即使设计里还有比它差得多的路径也一样:如果设计中只有少数几条问题路径,工具通常可以把精力集中在它们身上,从而满足时序约束。若其他路径的时序要求都容易满足,会带来很大帮助。
所以,为了得到一个能满足时序约束的设计,什么该做、什么不该做,并没有固定的规则。要在这方面做出正确判断,需要 FPGA 设计经验,也离不开对具体 FPGA 工具的了解。唯一永远正确的规则是:如果通过简单的 Verilog 修改就能改善时序,那就去改。不要偷懒,并且从一开始就把它做好。永远把时序放在心上。
写出跑得快的逻辑
前面已经说过,目标就是让寄存器之间的组合逻辑路径尽量短:用来计算寄存器下一状态的逻辑函数应当尽量简单。换句话说,实现这些逻辑函数所需的逻辑级数应当尽量少。
我们作为 FPGA 设计师的任务,就是审视 Verilog 代码,判断它最终会生成多复杂的逻辑函数。这需要了解综合器如何把 Verilog 转换成逻辑元件(LUT 以及其他逻辑原语(primitive))。这种知识来自经验积累,而分析时序报告正是积累经验的一种方式。更麻烦的是,不同 FPGA 的综合结果不尽相同。因此,写出能够产生快速逻辑的 Verilog 代码并不是一件容易的事。
如果你是一名 FPGA 新手,建议你花些时间去看看实现的结果,把这当作一种学习过程。时序报告会展示逻辑设计如何被拆解成一个个简单的逻辑元件;FPGA 工具也提供其他工具来查看这些底层逻辑元件。
此外,下面几条简单的规则也很有帮助:
- 流水线(pipeline):不要吝啬使用寄存器。在可行的情况下,把逻辑任务拆成小块,每一步后面都插入寄存器。FPGA 上有大量触发器(通常每个 LUT 旁边就有一个触发器),所以插入寄存器通常不会提高 FPGA 的资源利用率。唯一需要避免流水线的理由,是它会把设计变得过于复杂。
- 使用 if-then-else 时,尽量避免把很多 else 分支串联起来。如果能用 case 语句代替,通常更好。一个 else 分支往往要求一个逻辑函数来保证它前面的所有条件都不成立,因此多个 else 分支串联就可能需要很多级逻辑。
- 避免不必要的复位。尤其是同步复位(synchronous reset),会给逻辑函数增加一点额外的复杂度。而且无论同步还是异步复位,都会让布线变得更困难,因为这些复位信号需要到达大量逻辑元件。关于这个主题有一个单独的系列文章。
- 不要创建过于庞大的状态机(state machine)。状态数量多少算合适,并没有硬性限制。但如果状态数超过 20 个,你就应该考虑重新组织设计了。另外,要确保综合器对较大的状态机使用 one-hot 编码(大多数综合器默认就是这样)。这样做有助于生成快速的逻辑。
- 在 RAM 之后多放一个寄存器通常是更好的。我们来看这个隐式创建 RAM 的例子:
reg [7:0] array[0:127]; reg [7:0] val; reg [6:0] addr; always @(posedge clk) val <= array[addr];这段 Verilog 是正确的,但注意 @val 实际上是 RAM 的同步输出。也就是说,当时钟上升沿到来时,RAM 的读取操作才开始;只有从数组中取到值之后,@val 才会被更新。因此,与普通触发器相比,@val 的时钟到输出延迟(clock-to-output delay)比较大。这样一来,以 @val 为起点的路径就天然处于劣势。解决方法是再加一个寄存器:
reg [7:0] array[0:127]; reg [7:0] val_d, mem_out; reg [6:0] addr; always @(posedge clk) begin mem_out <= array[addr]; val_d <= mem_out; end注意,这段代码与原代码并不完全功能等价:这里的 @mem_out 才是 RAM 的输出,一个时钟周期之后它才被复制到 @val_d,所以 @val_d 并不是 @val 的简单替换。但 @val_d 是一个真正的寄存器,时钟到输出延迟很小。在很多 FPGA 上,这个额外的寄存器是块 RAM(block RAM)的一部分,所以并不会浪费触发器。我是不是说过,永远不要担心浪费触发器?
遗憾的是,增加这样一个寄存器往往会让设计明显变得更复杂。如果情况确实如此,那就不如不加这个寄存器,而是尽量让从 @val 出发的组合逻辑路径保持短一些。
另外两份时序报告
我在前面“没有比动脑更好的办法”一节里答应给出两份时序报告。我之所以把它们放在这里,而不是放在提到它们的地方,是因为它们很长,而且与上下文不是完全相关。
注意,这两份时序报告分别是相应场景下的关键路径,因此路径的起点和终点并不一定与前面展示的那条路径相同。
第一份时序报告对应第一个 Verilog 代码例子。与上文那份时序报告不同,这里允许工具使用专用算术单元。结果是时序约束轻松得到了满足。
这份时序报告是基于 Kintex UltraScale FPGA 生成的。在这个 FPGA 系列中,专用算术单元被称为 DSP48E2。注意,这条路径的起点和终点都在同一个 DSP48E2 上,所以逻辑延迟占了 100%。
Slack (MET) : 1.406ns (required time - arrival time) Source: calc_reg/DSP_A_B_DATA_INST/CLK (rising edge-triggered cell DSP_A_B_DATA clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg/DSP_OUTPUT_INST/ALU_OUT[10] (rising edge-triggered cell DSP_OUTPUT clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 2.445ns (logic 2.445ns (100.000%) route 0.000ns (0.000%)) Logic Levels: 4 (DSP_ALU=1 DSP_M_DATA=1 DSP_MULTIPLIER=1 DSP_PREADD_DATA=1) Clock Path Skew: -0.010ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.392ns = ( 7.392 - 4.000 ) Source Clock Delay (SCD): 4.096ns Clock Pessimism Removal (CPR): 0.694ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.264ns (routing 0.756ns, distribution 1.508ns) Clock Net Delay (Destination): 1.964ns (routing 0.696ns, distribution 1.268ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=80, routed) 2.264 4.096 calc_reg/CLK DSP48E2_X11Y34 DSP_A_B_DATA r calc_reg/DSP_A_B_DATA_INST/CLK ------------------------------------------------------------------- ------------------- DSP48E2_X11Y34 DSP_A_B_DATA (Prop_DSP_A_B_DATA_DSP48E2_CLK_A2_DATA[9]) 0.302 4.398 r calc_reg/DSP_A_B_DATA_INST/A2_DATA[9] net (fo=1, routed) 0.000 4.398 calc_reg/DSP_A_B_DATA.A2_DATA<9> DSP48E2_X11Y34 DSP_PREADD_DATA (Prop_DSP_PREADD_DATA_DSP48E2_A2_DATA[9]_A2A1[9]) 0.182 4.580 r calc_reg/DSP_PREADD_DATA_INST/A2A1[9] net (fo=1, routed) 0.000 4.580 calc_reg/DSP_PREADD_DATA.A2A1<9> DSP48E2_X11Y34 DSP_MULTIPLIER (Prop_DSP_MULTIPLIER_DSP48E2_A2A1[9]_U[10]) 0.994 5.574 f calc_reg/DSP_MULTIPLIER_INST/U[10] net (fo=1, routed) 0.000 5.574 calc_reg/DSP_MULTIPLIER.U<10> DSP48E2_X11Y34 DSP_M_DATA (Prop_DSP_M_DATA_DSP48E2_U[10]_U_DATA[10]) 0.164 5.738 r calc_reg/DSP_M_DATA_INST/U_DATA[10] net (fo=1, routed) 0.000 5.738 calc_reg/DSP_M_DATA.U_DATA<10> DSP48E2_X11Y34 DSP_ALU (Prop_DSP_ALU_DSP48E2_U_DATA[10]_ALU_OUT[10]) 0.803 6.541 r calc_reg/DSP_ALU_INST/ALU_OUT[10] net (fo=1, routed) 0.000 6.541 calc_reg/DSP_ALU.ALU_OUT<10> DSP48E2_X11Y34 DSP_OUTPUT r calc_reg/DSP_OUTPUT_INST/ALU_OUT[10] ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=80, routed) 1.964 7.392 calc_reg/CLK DSP48E2_X11Y34 DSP_OUTPUT r calc_reg/DSP_OUTPUT_INST/CLK clock pessimism 0.694 8.086 clock uncertainty -0.035 8.050 DSP48E2_X11Y34 DSP_OUTPUT (Setup_DSP_OUTPUT_DSP48E2_CLK_ALU_OUT[10]) -0.104 7.946 calc_reg/DSP_OUTPUT_INST ------------------------------------------------------------------- required time 7.946 arrival time -6.541 ------------------------------------------------------------------- slack 1.406
第二份时序报告对应第二个 Verilog 代码例子。在这个例子中,通过流水线(pipeline)处理改善了状况:
Slack (MET) : 0.433ns (required time - arrival time) Source: y_reg[1]__0/C (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg[23]/D (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 3.465ns (logic 1.653ns (47.706%) route 1.812ns (52.294%)) Logic Levels: 6 (CARRY8=4 LUT4=2) Clock Path Skew: -0.129ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.373ns = ( 7.373 - 4.000 ) Source Clock Delay (SCD): 4.040ns Clock Pessimism Removal (CPR): 0.538ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.208ns (routing 0.756ns, distribution 1.452ns) Clock Net Delay (Destination): 1.945ns (routing 0.696ns, distribution 1.249ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=121, routed) 2.208 4.040 clk_IBUF_BUFG SLICE_X54Y88 FDRE r y_reg[1]__0/C ------------------------------------------------------------------- ------------------- SLICE_X54Y88 FDRE (Prop_EFF2_SLICEL_C_Q) 0.138 4.178 r y_reg[1]__0/Q net (fo=25, routed) 0.505 4.683 y[1] SLICE_X53Y91 LUT4 (Prop_B6LUT_SLICEM_I0_O) 0.150 4.833 r calc[7]_i_28/O net (fo=1, routed) 0.344 5.177 calc[7]_i_28_n_0 SLICE_X53Y89 CARRY8 (Prop_CARRY8_SLICEM_DI[2]_CO[7]) 0.424 5.601 r calc_reg[7]_i_9/CO[7] net (fo=1, routed) 0.043 5.644 calc_reg[7]_i_9_n_0 SLICE_X53Y90 CARRY8 (Prop_CARRY8_SLICEM_CI_O[0]) 0.122 5.766 r calc_reg[23]_i_30/O[0] net (fo=3, routed) 0.402 6.168 calc_reg[23]_i_30_n_15 SLICE_X51Y88 LUT4 (Prop_C5LUT_SLICEL_I0_O) 0.169 6.337 r calc[15]_i_8/O net (fo=1, routed) 0.437 6.774 calc[15]_i_8_n_0 SLICE_X51Y92 CARRY8 (Prop_CARRY8_SLICEL_DI[1]_CO[7]) 0.422 7.196 r calc_reg[15]_i_1/CO[7] net (fo=1, routed) 0.030 7.226 calc_reg[15]_i_1_n_0 SLICE_X51Y93 CARRY8 (Prop_CARRY8_SLICEL_CI_O[7]) 0.228 7.454 r calc_reg[23]_i_1/O[7] net (fo=1, routed) 0.051 7.505 calc_reg[23]_i_1_n_8 SLICE_X51Y93 FDRE r calc_reg[23]/D ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=121, routed) 1.945 7.373 clk_IBUF_BUFG SLICE_X51Y93 FDRE r calc_reg[23]/C clock pessimism 0.538 7.910 clock uncertainty -0.035 7.875 SLICE_X51Y93 FDRE (Setup_HFF_SLICEL_C_D) 0.063 7.938 calc_reg[23] ------------------------------------------------------------------- required time 7.938 arrival time -7.505 ------------------------------------------------------------------- slack 0.433
这个改进幅度不如使用专用算术单元时那么明显,但已经足以满足时序约束了。
到这里,关于时序收敛的一般性讨论就结束了。下一页会给出一些实用的应对策略。