本部分主要参考米联客的视频教程:1 概述 - 米联客视频课程https://www.uisrc.com/lesson-730.html
1、时钟
1.1 时钟概念
时钟信号,一般指控制触发器或其他器件状态变化的周期性脉冲信号,数据可以在其上升沿或者下降沿发生变化。时钟理想模型是一个占空比为50%的方波。

1.2 时钟的关键参数
时钟的理想模型是一个方波,但是实际上,信号无法变化如此迅速,基本上存在缓慢的电平上升和下降的过程,如下图所示:

并且,时钟通过外部晶振进入到FPGA器件中时,会产生延迟、偏移、抖动、失真等现象,因此在设计时需要考虑这些关键参数,以保证设计的整体功能正常。
1.2.1 时钟转换时间
时钟转换时间,指的就是高电平到低电平转换,或者低电平到高电平转换的时间,即时钟上升沿或下降沿的时间。
1.2.2 时钟延迟
时钟从时钟源(晶振、PLL等)到目的地(触发器时钟端口等)的延迟,称为时钟延迟。
时钟延迟分为两种:
1、时钟源延迟:时钟从实际时钟源,到芯片外部端口或引脚的传输时间。
2、时钟网络延迟:时钟从端口或引脚,经过缓冲器或者内部连线的延迟时间。

1.2.3时钟偏移 skew
时钟不可能同一时刻到达所有的寄存器,这种现象就称为时钟偏移。时钟偏移反映了不同寄存器时钟端口之间的差异。

1.2.4 时钟抖动 jitter
同一时钟源,两个时钟周期之间存在插值,即为时钟抖动。主要是晶振或者PLL内部电路导致的差异。

时钟抖动的一个计算公式为:jitter=|T1-T2|。 引起时钟抖动的原因主要包括:
a、外部输入晶振或时钟管理芯片的精度不高;
b、温度、电源干扰、电磁干扰等影响;
c、FPGA品质和工艺影响;
d、PCB传输过程中阻抗不匹配问题造成的畸变。
1.2.5 时钟信号设计时的注意事项
时钟通过外部晶振或时钟芯片,进入到FPGA内部后,会产生延迟、抖动或者失真等情况,所以硬件设计和FPGA软件设计时,都需要考虑时钟的影响。
a、选择时钟稳定性更佳、抖动更合适的晶振或时钟管理芯片,一些工业场景还应该考虑温度对时钟的影响;
b、时钟信号应该走专用的FPGA时钟引脚;
c、FPGA软件设计中,应该采用跨时钟处理的处理方法,且进行正确的时钟约束。
2、触发器
2.1 触发器的基本概念
能够存储1位二进制信号的基本单元电路,称为触发器。触发器常用于构建各种时序电路单元:寄存器、计数器、状态机等。下图是常见的D触发器框图。

2.2 建立时间和保持时间
由触发器,则可以引申出建立时间和保持时间的概念。

建立时间(setup time):时钟边沿到来之前,数据必须保持稳定的时间。
保持时间(hold time):时钟边沿到来之后,数据必须保持稳定的时间。
输出延迟(Tco):D触发器采集到D端口的输入数据后,数据不会立即输出到Q端,而是需要经过一段延时才会输出。
触发器的时钟与数据的时序关系,需要满足建立时间和保持时间的要求,数据才能正常输出,若不满足,可能产生亚稳态(即Q处于不确定状态,可能为0,也可能为1)。
3、静态时序概要
布局布线工具会根据不同时钟的周期约束、不同信号的时序路径约束,对逻辑功能模块进行自动地布局布线,调整高速信号和低速信号之间的布局布线方式,以实现最佳的电路时序。
3.1 时序路径模型
本小节,简要介绍一下常用的几种时序路径。下图是典型的集中时序路径,主要分为path1、path2、path3、path4。

3.1.1 path1
由 FPGA 引脚,到D触发器输入端的路径。此路径需要关注外部输入时,输入信号与时钟信号之间的时序关系:即需要考虑外部芯片(查询芯片手册)和PCB电路布线延迟。对应着输入延迟约束input_delay,后续详细介绍。
3.1.2 path2
D触发器输出端,到另一个D触发器输入端的路径。此路径完全是FPGA内部的路径,也是最使用的时序路径,后续详细介绍。
3.1.3 path3
D触发器输出端,到FPGA引脚的路径。此路径需要关注输出到外部芯片时,输出信号与时钟信号之间的时序关系:即需要考虑外部芯片(查询芯片手册)和PCB电路布线延迟。对应着输出延迟约束output_delay,后续详细介绍。
3.1.4 path4 :
由FPGA 引脚,到另一个FPGA引脚的路径。此路径只有FPGA内部的组合逻辑延迟和布局布线线延迟,没有经过触发器。一般使用max_delay和min_delay进行约束,后续详细介绍。
3.2 数据路径

数据路径:数据从源寄存器的时钟引脚开始,到目的寄存器的输入引脚,所经过的路径;
时钟路径:时钟从源端(可能是锁相环的输出)开始,到寄存器的时钟引脚,所经过的路径;

数据到达路径:数据从源寄存器传输到目的寄存器的实际路径,其路径延时称为数据到达时间,其值为数据路径延时与源时钟路径延时之和;
数据需求路径:为了使数据满足相应的建立时间和保持时间要求,数据理论上应该传输时间的计算路径,其路径延时称为数据需求时间;
3.3 发射边沿与输出边沿

发射边沿:源寄存器发送数据的时钟边沿;
锁存边沿:目的寄存器捕获数据的时钟边沿;一般,锁存边沿比发射边沿晚1个时钟周期;
3.4 建立时间和保持时间之间的关系

对于建立时间而言,数据在源寄存器,被时钟锁存并发送后,在下个时钟周期的上升沿才会被目的寄存器锁存。如上图所示,如果数据从源寄存器,到目的寄存器存在更多的延迟,从第二个时钟边沿来看,建立时间会变少;
对于保持时间而言,保持时间分析应该分析当前数据的发射边沿和锁存边沿,如果数据从源寄存器到目的寄存器的延迟太小,数据更新太快,从而导致数据的保持时间变少,可能会无法捕获到信号;如上图所示,如果数据从源寄存器,到目的寄存器存在更多的延迟,从第二个时钟边沿来看,保持时间会变大;
对于上图中,最大输入延迟,影响了数据的建立时间。即数据从源寄存器发射后,如果数据路径延时时间过大,导致下一个时钟边沿时,目的寄存器的建立时间不满足(或者数据根本没有到达目的寄存器),从而形成亚稳态;
对于上图中,最小输入延迟,影响了数据的保持时间。即数据从源寄存器发射后,如果数据路径延时过小,导致下个时钟边延时,目的寄存器的保持时间不满足(即数据来的太快,在时钟上升沿时,保持稳定的时间太少),从而形成亚稳态。
3.5 建立时间裕量和保持时间裕量
时间裕量,指的是实际的时间延迟与最大允许的时间延迟之间的差值。在时序分析中,如果一个路径的时间裕量为正数,那么表明此路径满足时序要求,反之则不满足。在FPGA设计中,可以通过优化逻辑、优化布局布线、各种约束手段等方式改善这些裕量,从而提升设计的可靠性。

上图中,C代表时钟,D代表数据。可以看到在第二个时钟的上升沿进行数据的锁存,图中标识了数据的建立时间裕量setup slack,建立时间Tsu,保持时间Th, 保持时间裕量hold slack。
建立时间裕量setup slack:时钟边沿到来前,数据已经稳定的时间,与建立之间之间的差值;
保持时间裕量hold slack:时钟边沿到来后,数据依旧保持稳定的时间,与保持时间之间的差值;
4、时钟约束
4.1主时钟约束
FPGA的主时钟,主要来源于外部时钟引脚,或者高速收发器GT内部产生。主时钟必须与一个网表对象相连,该对象表示所有时钟边沿的开始点,并在时钟树中,向下传递。即,主时钟的原点定义了0时刻,vivado中以此为0时刻来计算时钟的延迟。
4.1.1 外部时钟引脚输入
主时钟的约束语法如下:声明了某个引脚中的时钟名称、时钟周期、以及时钟上升沿和下降沿的时刻,单位为ns。
create_clock -name <clock_name> -period <clock_period> -waveform {<rise_time> <fall_time>} [get_ports <input_port>]
#实际举例,一个10Mhz的,占空比为50%的时钟
create_clock -name clock_10M -period 100 -waveform {0 50} [get_ports clk_in1]
#实际举例,一个10Mhz的,占空比为50%的时钟,相比较上面的时钟存在180度相位差
create_clock -name clock_10M_phase -period 100 -waveform {50 100} [get_ports clk_in2]
#若主时钟为差分时钟,则只需要约束差分时钟的P端即可。
create_clock -name clock_10M -period 100 -waveform {0 50} [get_ports clk_in_p]
4.1.2 高速收发器内部产生的时钟
GT等IP核内部产生的时钟,有时候会用作主时钟,其约束如下:
#GT内部时钟用作主时钟
create_clock -name sys_clk -period 10 [get_pins GT/TXOUTCLK]
4.2生成时钟约束
生成时钟,也叫衍生时钟,是由主时钟经过时钟管理单元(PLL/MMCM、BUFR、或verilog逻辑)生成的时钟信号。生成时钟的约束必须指定时钟源,此时钟源可以是主时钟也可以是其他衍生时钟。
生成时钟只能约束其与主时钟的倍频、分频关系,不能直接定义周期。一般只有自行编写逻辑的生成时钟才需要手动约束,因为vivado无法识别。 约束如下;
#衍生时钟约束,约束FPGA内部信号
create_generated_clock -name <generated_clock_name> -source <master_clock_pin_or_port> -multiply_by <mult_factor> -divide_by <div_factor> [get_pins <pin>]
#衍生时钟约束,约束FPGA引脚时钟
create_generated_clock -name <generated_clock_name> -source <master_clock_pin_or_port> -multiply_by <mult_factor> -divide_by <div_factor> [get_ports <port>]
#举例1
#主时钟
create_clock -name sysclk -period 100 -waveform {0 50} [get_ports clk_in1]
#生成时钟
create_generated_clock -name rxclk -source sysclk -multiply_by 2 -divide_by 1 [get_pins data_rx/rxclk]
#举例2
#或直接约束生成时钟
create_generated_clock -name rxclk -source [get_ports clk_in1] -multiply_by 2 -divide_by 1 [get_pins data_rx/rxclk]
4.3 虚拟时钟约束
虚拟时钟,不是实际上存在的时钟,而是为了进行时序分析,而人为定义的一个时钟。
当FPGA设计中,相关信号不存在对应的FPGA时钟网络时,就需要设计虚拟时钟,用于灵活的管理与外部器件的数据的时序关系。
虚拟时钟的约束如下:(与主时钟约束基本一致,区别是,不需要指定引脚或端口,需要指定相关数据信号的约束)
#虚拟时钟的约束语句与实际时钟的约束语句基本一致,区别是虚拟时钟的约束不需要指定信号端口或引脚。
create_clock -name <clock_name> -period <clock_period> -waveform {<rise_time> <fall_time>}
#虚拟时钟约束时,需要约束数据信号
#若有数据输入到FPGA,则约束输入数据
set_input_delay -clock <clock_name> <time> [get_ports <date_ports>]
#若有数据输出到外部器件,则约束输出数据
set_output_delay -clock <clock_name> <time> [get_ports <date_ports>]
#举例如下:
#约束一个20ns周期的虚拟时钟,并以此设计data_in信号的输入延迟
create_clock -name vir_clk -period 20 -waveform {0 10}
set_input_delay -clock vir_clk 4 [get_ports data_in]
使用虚拟时钟的主要应用场景:
1、输入到FPGA的数据信号没有同步时钟,需要在FPGA内部控制布线延迟。

上图中,假设sysclk为50Mhz时钟,PLL倍频后输出时钟pllclk为125Mhz。dina信号和dinb信号经过FPGA引脚输入到内部的D触发器,由于sysclk与pllclk不是整数倍关系,此时就需要使用虚拟时钟解决时序分析困难的问题。
create_clock -period 20 -name sysclk [get_ports sysclk]
set_input_delay -clock sysclk 4 [get_ports dina]
#使用虚拟时钟约束,告知延迟时间,以及其所在的时钟域,仅影响布局布线
create_clock -period 8 -name vir_clk
set_input_delay -clock vir_clk 4 [get_ports dinb]
2、FPGA设计验证系统中,需要对dev器件在不同的延迟情况下进行验证性设计。

上图中可以看到,输入到dev和fpga的时钟是同一个时钟,但是输入到dev的时钟存在1ns的延迟。假设dev到fpga的数据路径最大延迟是4ns,最小延迟是2ns,那么可以做约束如下:
#约束主时钟
create_clock -name sysclk -period 20 -waveform {0 10} [get_ports sysclk]
#约束虚拟时钟 根据硬件上的1ns时钟路径延时,虚拟时钟相比较主时钟,需要存在1ns延迟
create_clock -name vclk -period 20 -waveform {1 10}
#最大延迟4ns,最小延迟2ns
set_input_delay -clock vclk -min 2 [get_ports data_in]
set_input_delay -clock vclk -max 4 [get_ports data_in]
3、使用虚拟时钟约束输出信号

上述输出模型中,没有同步时钟输出,但是对于douta和doutb两个信号,从寄存器到引脚有输出延迟路径的要求,此时就可以定义2个虚拟时钟,来约束douta和doutb。
假设系统时钟sysclk为50Mhz采样dina,PLL输出时钟pllclk为125Mhz时钟采样dinb。
#约束主时钟
create_clock -name sysclk -period 20 -waveform {0 10} [get_ports sysclk]
#约束虚拟时钟 douta
create_clock -name vclk_a -period 20 -waveform {0 10}
#douta最大延迟4ns,最小延迟2ns
set_output_delay -clock vclk_a -min 2 [get_ports douta]
set_output_delay -clock vclk_a -max 4 [get_ports douta]
#约束虚拟时钟 doutb
create_clock -name vclk_b -period 8 -waveform {0 4}
#doutb最大延迟-1ns,最小延迟-2ns
set_output_delay -clock vclk_b -min -2 [get_ports doutb]
set_output_delay -clock vclk_b -max -1 [get_ports doutb]
4.4 时钟特性约束
前面介绍过时钟的概念和相关参数。时钟源本身抖动、外部的噪声、传输延迟、时钟信号的漂移、失真等,统称为时钟的不确定性。时钟的不确定性,主要受始终偏移和抖动的影响。
4.4.1 set_clock_uncertainty
如果需要在某个时钟路径上、或者两个时钟之间的时序路径上增加额外的裕量slack,就可以使用set_clock_uncertainty命令为不同的角点、延迟或特定的始终关系定义额外的时钟不确定性。
#时钟不确定性约束模板
set_clock_uncertainty -setup/hold -from [get_clocks <clock1_name>] -to [get_clocks <clock2_name>] <uncertainty_value>
#没有-setup/hold字符,则表示同时约束建立时间和保持时间
set_clock_uncertainty -from [get_clocks <clock1_name>] -to [get_clocks <clock2_name>] <uncertainty_value>
#示例
#使sysclk时钟域下所有的路径上的裕量收紧500ps
set_clock_uncertainty -from sysclk -to sysclk 0.5
4.4.2 set_clock_latency
始终延迟指的是信号在各节点传播所需要的时间,详细参考上面章节。始终约束语法如下:
#约束模板
set_clock_latency [-rise] [-fall] [-max/-min] [-source/network] [pins/ports/cells]
