时序分析
建立保持时间修复
建立时间修复 | 保持时间修复 |
降低时钟频率 | |
增大时钟正偏斜 | 减小时钟正偏斜 |
减小时钟负偏斜 | 增大时钟负偏斜 |
减小组合逻辑延时 (具体表现为插入buffer,xilinx还可插入LUT1),关键路径处理 |
增加组合逻辑延时 |
减少多扇出网络 | |
选用延时较小的CELL |
关键路径
关键路径指的是同步逻辑电路中,组合时延的最大路径,对性能设计起到决定性影响的时序路径。读关键路径的优化可以直接提高系统性能。
对于 同步逻辑,时序优化的方法有:
pipeline(插入流水线)
在路径上插入额外的寄存器,称作插入流水线,多用于高度流水的设计中。这种插入寄存器增加时钟周期延时并不会影响设计总体功能的实现,也就是在保持吞吐量不变的情况下改善设计的时序性能。
在插入寄存器时候,要在组合逻辑路径的合适位置插入,使得寄存器插入后的路径被分割的几个部分的组合逻辑延时基本一致。
流水线冒险:结构冒险、数据冒险、控制冒险。解决办法就是在流水线中插入流水线气泡直至冒险排除。
结构冒险:由于资源冲突使得硬件无法支持所有可能的指令组合同时进行。例如在单端口存储器执行读写操作,假设流水线要求同一个时钟周期内对存储器访问两次,就会产生读写冲突。这种情况就可以增加一个时钟周期,就是将流水线停止一个周期(产生流水线气泡)。另一种方法就是使用不同存储器或多端口存储器,会消耗更多资源。
数据冒险:指令执行需要之前指令的计算结果,而这个结果在流水线中还没计算出来。解决办法就是使用数据/寄存器转移,纵向气泡延迟。
控制冒险:分支的流水线和其他指令改变计数器的值。由于分支所需的数值需要后续计算流水才可以得到,这时我们需要将流水停止几个周期直到重新获取下一条数值进行判断。
最快时钟频率和流水线设计思想:同步电路的速度等价于同步时钟频率,时钟越快,处理数据时间间隔越短,电路单位时间内处理的数据量就越大。时钟延时必须大于Tco + Tdata + Tsetup。由于Tco 和 Tsetup是由器件工艺决定,在设计中只能改变Tdata来实现时钟提升。
而流水线设计:是将原本一个时钟周期需要完成的较大的组合逻辑通过合理切割后由多个时钟周期完成,插入寄存器后,时钟频率明显提升,但是面积会增加。
寄存器平衡(retiming)
在不增加寄存器的前提下,通过改变寄存器在路径上的位置,从而实现关键路径的时序优化。
操作符平衡(加法树、乘法树)
消除代码优先级
本身不需要优先级的地方,可以用case代替if else,使得顺序执行的语句并行执行。如果有优先级要求,就不能这样做。
逻辑复制
当某个信号的扇出fanout较大,会造成这个信号到达各个目的节点的路径变得过长,从而形成关键路径,此时可以通过对该信号进行复制来降低扇出。高扇出的危害是大大增加布局布线的难度。
关键信号后移
关键输入应该在逻辑最后一级提供,其中关键输入为芯片、Slice、或者LUT提供的时延最大的输入,比如在if…else if…链中,将关键信号放在第一级。
静态时序分析
通过静态时序分析可以找出关键路径。
静态时序分析的目的
验证数字电路的时序是否违例 |
前提是同步时序逻辑,不能分析异步电路 |
计算路径延时总和,并比较相对于预定义时钟的延迟 |
仅关心时序,不关心逻辑 |
穷尽的思想,对所有的路径进行分析,不需要动态激活,速度快,在同步时序达到100%覆盖 |
目的是找到隐藏的时序问题,根据时序分析结果优化逻辑或约束,达到时序闭合 |
识别范围:建立保持时间、恢复移除时间、最小最大跳变、时钟脉冲宽度、总线竞争、关键路径、时钟偏移抖动、约束冲突等 |
$setup(data,posedge clk,tSU);
$hold(posedge clk,data,tHLD);
$setuphold(posedge clk,data, tSU, tHLD);
$recovery(posedge rst,posedge clk,3);
$removal(posedge rst,posedge clk,3);
$recrem(posedge rst,posedge clk,recovery_limit,removal_limit);
STA代表性工具:DC综合、布局布线(ICC2)、时序分析工具(Prime Time),vivado包含所有
时序约束
时序约束指在逻辑综合、布局布线或静态时序分析时候,在工具中指定信号频率、占空比、时间延迟等约束。
附加时序约束,一般策略是先附加全局约束,然后
(1)梳理时钟树
(2)查看时钟之间是否有交互
(3)约束顺序
主时钟约束 | |
衍生时钟约束 (额外:pll生成时钟命名 异步时钟声明) | |
延迟约束 |
输入管脚:1.捕获时钟是主时钟 直接set_input_delay 2.捕获时钟是衍生 时钟 先创建虚拟时钟 再delay 输出管脚 1.有输出随路时钟(比如SPI传输的sclk) 直接set_output_delay 2.无随路时钟(I2C UART)创建虚拟时钟 再delay |
伪路径约束 |
1.异步复位需要添加伪路径 2.异步时钟 如果已经用asynchronous约束异步时钟组,就无须伪路径进行约束 |
多周期路径 | 1.带有使能的数据 2.两个数据交互的时钟之间是否存在相位差 3.存在快慢时钟路径 |
一般工程
1.虚拟时钟 一般工程基本用不到,需要设计虚拟时钟的场景,一般通过设计来保证时序收敛,设置虚拟时钟意义不大。虚拟时钟即 FPGA 内部不存在的时钟,主要用于辅助做一些分析。
2.output_delay FPGA最后一级寄存器到输出路径,往往使用 IOB(IO block),因此最后一级寄存器位置固定,buffer到pad的走线延时
是确定的。在这种情况,是否满足时序要求完全取决于设计,做约束只是验证时序是否收敛,所以基本不做。
***********input_delay是必要的,这是上一级器件输出的时序关系
3.多周期路径,应用场景很多,但是实际中根据Timing report进行约束,即时应用场景存在,但是没有任何时序warning,往往不需要添加约束
4.如果多周期约束添加后还是提示 Intra_Clocks Paths 的setup time不过,就需要查看程序规范性 避免always时序逻辑中begin end之间的if else对过多,
从而导致一个周期不能完成所有的 if else 的判断赋值,可以将多个组的 if else 拆分到多个always时序逻辑执行
时序仿真
功能仿真
功能仿真是指在一个设计中,在设计实现前对所创建的逻辑进行验证其功能是否正确的过程。布局布线以前的仿真都称作功能仿真,他包括综合前仿真和综合后仿真。综合前仿真主要针对基于原理框图的设计;综合后仿真既适合原理图的设计,也适合基于HDL语言的设计。
时序仿真
使用布局布线后器件给出的模块和连线的延时信息,在最坏的情况下对电路的行为作出实际的估计。时序仿真使用的仿真器和功能仿真使用的仿真器是相同的,所需要的流程和激励也是相同的;
唯一的差别是时序仿真加载到仿真器的设计包括基于实际布局布线的最坏情况的延时,并且在仿真结果波形中,时序仿真后的信号加载了延时,而功能仿真没有。
时序优化
优化流程
时序报告
1.ILA相关约束可以忽略
2.Report timing summary可以打印所有路径报告,方便查看哪些违例了。
时序违例
跨时钟域
伪路径
(1)复位信号,选择point到point,否则所有信号都 false了
(2)跨时钟域信号,可以选择clock到clock。
多周期路径
两级寄存器之间有复杂的组合逻辑,导致延迟可能超过1个时钟周期。
同一个时钟域
1.synthesis
点击 SYNTHESIS --- Synthesis,在Options界面可以选择不同的综合策略,时序改善余地不大。
2.implementation
点击 IMPLEMENTTION --- implementation,在Options界面可以选择不同的综合策略,进行时序改善。
3、布局布线物理优化(div_timing)
例如乘法器除法器常常出现问题,可以用此方法解决。
4、插入流水线寄存器(实实在在的办法)
(1)除法器IP核里增大latency,会给第一个计算结果带来延迟,但是增加了时序余量,改善了时序。
(2)组合逻辑带来延时,采用流水线打拍来改变代码,增大了latency,改善了时序。
流水线优化实例
`timescale 1ns / 1ps
//**************************************************************************
// *** 名称 : top_encode.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020-04-12
// *** 描述 : 流水线案例,流水线优化后
//**************************************************************************
module top_encode
//========================< 端口 >==========================================
(
//input -----------------------------------------
input wire sysclk_in ,
input wire pkg_valid ,
input wire [15:0] in_data0 ,
input wire [15:0] in_data1 ,
input wire [15:0] in_data2 ,
input wire [15:0] in_data3 ,
input wire [15:0] in_data4 ,
input wire [15:0] in_data5 ,
input wire [15:0] in_data6 ,
input wire [15:0] in_data7 ,
//output ----------------------------------------
output reg o_encode_valid ,
output reg [31:0] o_encode
);
//========================< 信号 >==========================================
//reg -------------------------------------------
reg pkg_valid_r ;
reg [15:0] in_data0_r ;
reg [15:0] in_data1_r ;
reg [15:0] in_data2_r ;
reg [15:0] in_data3_r ;
reg [15:0] in_data4_r ;
reg [15:0] in_data5_r ;
reg [15:0] in_data6_r ;
reg [15:0] in_data7_r ;
//pll -------------------------------------------
wire sclk ;
wire clk150 ;
wire clk300 ;
//no pipelined ----------------------------------
wire [15:0] code_0 ;
wire [15:0] code_1 ;
wire [15:0] code_2 ;
wire [15:0] code_3 ;
wire [15:0] code_4 ;
wire [15:0] code_5 ;
wire [15:0] code_6 ;
wire [15:0] code_7 ;
wire [31:0] code_sum ;
//pipelined -------------------------------------
reg [15:0] code_0_r ;
reg [15:0] code_1_r ;
reg [15:0] code_2_r ;
reg [15:0] code_3_r ;
reg [15:0] code_4_r ;
reg [15:0] code_5_r ;
reg [15:0] code_6_r ;
reg [15:0] code_7_r ;
reg [31:0] code_sum_r ;
reg pkg_valid_rr ;
reg pkg_valid_rrr ;
//==========================================================================
//== pll
//==========================================================================
clk_gen clk_gen
(
.clk_out1 (clk150 ),
.clk_out2 (clk300 ),
.clk_in1 (sysclk_in )
);
assign sclk = clk150;
//==========================================================================
//== no pipelined
//==========================================================================
always @(posedge sclk) begin
pkg_valid_r <= pkg_valid;
in_data0_r <= in_data0;
in_data1_r <= in_data1;
in_data2_r <= in_data2;
in_data3_r <= in_data3;
in_data4_r <= in_data4;
in_data5_r <= in_data5;
in_data6_r <= in_data6;
in_data7_r <= in_data7;
end
assign code_0 = in_data0_r | in_data7_r;
assign code_1 = in_data1_r | in_data6_r;
assign code_2 = in_data2_r | in_data5_r;
assign code_3 = in_data3_r | in_data4_r;
assign code_4 = in_data0_r + in_data1_r + in_data2_r + in_data3_r;
assign code_5 = in_data1_r + in_data2_r + in_data3_r + in_data4_r;
assign code_6 = in_data2_r + in_data3_r + in_data4_r + in_data5_r;
assign code_7 = in_data3_r + in_data4_r + in_data5_r + in_data6_r;
assign code_sum = code_0 + code_1 + code_2 + code_3 + code_4 + code_5 + code_6 + code_7;
always @(posedge sclk) begin
o_encode_valid <= pkg_valid_r;
o_encode <= code_sum;
end
/*
//==========================================================================
//== pipelined
//==========================================================================
//一级寄存器
always @(posedge sclk) begin
code_0_r <= code_0;
code_1_r <= code_1;
code_2_r <= code_2;
code_3_r <= code_3;
code_4_r <= code_4;
code_5_r <= code_5;
code_6_r <= code_6;
code_7_r <= code_7;
end
//二级寄存器
always @(posedge sclk ) begin
code_sum_r <= code_0_r + code_1_r + code_2_r + code_3_r + code_4_r + code_5_r + code_6_r + code_7_r;
end
//==========================================================================
//== 信号对齐
//==========================================================================
always @(posedge sclk ) begin
pkg_valid_rr <= pkg_valid_r;
pkg_valid_rrr <= pkg_valid_rr;
end
//==========================================================================
//== 输出端口
//==========================================================================
always @(posedge sclk) begin
o_encode_valid <= pkg_valid_rrr;
o_encode <= code_sum_r;
end
*/
endmodule
注意:平常写代码时:case别写太深、if else里不要嵌套 case、if 条件的条件位宽尽量窄(减少不同位之间的扇出差异)