数字设计中,“时钟”表示在寄存器间可靠地传输数据所需的参考时间。Vivado的时序引擎通过时钟特征来计算时序路径需求,通过计算裕量(Slack)的方法报告设计时序空余。时钟必须有合适的定义,包含如下特性:
如下图给出了两个时钟Clk0: period=10, waveform={0 5}、Clk1: period=8, waveform = {2 8}。
上述给出的只是时钟的理想特征。当时钟进入了FPGA器件,通过时钟树传递时,时钟边沿会有延时,通常称作时钟网络延迟;噪声或硬件表现会导致时钟随时可能发生变化,通常称作时钟不确定性,包括时钟抖动、相位错位等等。Vivado在时序分析时会考虑这些非理想因素以得到精确的时序裕量。
Xilinx FPGA器件内部有专用的硬件资源,支持大量设计时钟的使用。通常板子上有一个外部组件(如有源晶振)产生时钟信号,通过输入端口进入器件内部。外部时钟可以通过MMCM、PLL、BUFR等特殊原语生成其它时钟,也可以由LUT、寄存器等常规单元进行转换(通常称作门控时钟)。本文将讲述如何根据应用情况定义时钟。
主时钟通常由两个来源:(1).板级时钟通过输入端口进入设计;(2).GT收发器的输出管脚(如恢复时钟)。主时钟必须与一个网表对象相连,该对象代表了所有时钟边沿的开始点,并且在时钟树中向下传递。也可以说,主时钟的源点定义了0时刻,Vivado靠此来计算时钟延迟和不确定性。
主时钟只能通过create_clock命令来定义,且必须放在约束的开始,这是因为其它时序约束几乎都要参考主时钟。下面给出两个主时钟的例子。第一个例子如下图所示,采用单端时钟输入:
板级时钟通过sysclk端口进入FPGA,通过一个输入缓冲器和一个时钟缓冲器后到达寄存器。使用如下命令定义:
create_clock -period 10 [get_ports sysclk] #10ns周期,50%占空比,无相移
create_clock -name devclk -period 10 -wavefor {2.5 5} [get_ports sysclk] #板级时钟名称devclk,10ns周期,25%占空比,90°相移
第二个例子如下图所示,采用差分时钟输入,这也是高速时钟的输入方式:
上图中差分时钟驱动一个PLL,定义主时钟时必须只创建差分缓冲器的正极输入。如果同时创建了正极、负极输入,将会导致错误的CDC路径。如“create_clock -name sysclk -period 3.33 [get_ports SYS_CLK_clk_p]”。
这种类型的时钟对于初学者来说用的可能很少,虚拟时钟通常用于设定输入和输出的延迟约束。之所以称为“虚拟”,是因为这种时钟在物理上没有与设计中的任何网表对象相连。定义时使用create_clock命令,但无需指定源对象。在下列情况需要用到虚拟时钟:
比如时钟clk_virt的周期为10ns,且不与任何网表对象相连,可以这样定义“create_clock -name clk_virt –period 10”,没有指定objects参数。注意,虚拟时钟必须在使用之前便定义好。
生成时钟是指在设计内部由特殊单元(如MMCM、PLL)或用户逻辑驱动的时钟。生成时钟与一个上级时钟(注:官方称作master clock,为与primary clock作区分,这里称作上级时钟)相关,其属性也是直接由上级时钟派生而来。上级时钟可以是一个主时钟,也可以是另一个生成时钟。
生成时钟使用create_generated_clock命令定义,该命令不是设定周期或波形,而是描述时钟电路如何对上级时钟进行转换。这种转换可以是下面的关系:
Vivado计算生成时钟的延迟时,会追踪生成时钟的源管脚与上级时钟的源管脚之间的所有组合和时序路径。某些情况下可能只希望考虑组合逻辑路径,在命令行后添加-combinational选项即可。
这里先解释一下本文甚至本系列大量使用的两个词,端口(Port)和管脚(Pin)。端口通常用get_ports命令获取,管脚使用get_pins命令获取。二者的含义是不同的,但管脚的范围更广泛,比如设计中用到的一个寄存器都有3个管脚:clk、D和Q。下面给出几个定义生成时钟的例子:
下图中,主时钟clkin通过端口进入FPGA,使用一个寄存器REGA对其2分频,得到的生成时钟clkdiv2驱动其它的寄存器管脚。
可以采用如下两种方法对生成时钟进行约束:
#定义主时钟,周期10ns,50%占空比
create_clock -name clkin -period 10 [get_ports clkin]
#约束方法1,主时钟作为源点
create_generated_clock -name clkdiv2 -source [get_ports clkin] -divide_by 2 [get_pins REGA/Q]
#约束方法2,REGA的始终管脚作为源点
create_generated_clock -name clkdiv2 -source [get_pins REGA/C] -divide_by 2 [get_pins REGA/Q]
约束命令中使用**-source选项来设定上级时钟,但如上所示,该选项只能设定为一个端口或管脚类型的网表对象,不能直接设置为时钟类型对象。上面约束使用-divide_by选项设置分频系数,此外还可以使用-edges**选项,如下所示:
#该约束与上面等效
create_generated_clock -name clkdiv2 -source [get_pins REGA/C] -eedges {1 3 5} [get_pins REGA/Q]
-edges的参数为一个列表,该列表通过主时钟的边沿来描述生成时钟的波形。列表中的值为主时钟边沿的序号(注意观察上图),由时钟上升沿开始,定义了生成时钟边沿的时间点。
如果仅需要改变时钟的相移,使用**-edge_shift**选项可以正向或反向设定每一个生成时钟波形的相移量。注意,-edge_shift选项不能与-devide_by、-multiply_by、-invert选项同时使用。下图中上级时钟为clkin,进入mmcm0单元,产生一个25%占空比、相移90°的时钟:
可以采用如下方法对生成时钟进行约束。使用上级时钟的1、2、3标号边沿(即0ns、5ns、10ns)定义生成时钟,为了得到预期波形,1和3标号边沿要分别移动2.5ns,得到2.5ns、5ns、12.5ns的波形。
#定义主时钟,周期10ns,50%占空比
create_clock -name clkin -period 10 [get_ports clkin]
#定义生成时钟,周期10ns,25%占空比,90°相移
create_generated_clock -name clkshifit -source [get_pins mmcm0/CLKIN] -edges {1 2 3} -edge_shift {2.5 0 2.5} [get_pins mmcm0/CLKOUT]
这种情况通常用于定义MMCM或PLL的输出,一般使用这些IP核时会自动创建相应约束。考虑上例中的图,假设MMCM将上级时钟倍频到4/3倍,无法直接倍频,需要同时使用-divede_by和-multiply_by选项来实现:
create_clock -name clkin -period 10 [get_ports clkin] #定义主时钟
#定义生成时钟,4/3倍频
create_generated_clock -name clk43 -source [get_pins mmcm0/CLKIN] -multiply_by 4 -divide_by 3 [get_pins mmcm0/CLKOUT]
前面简单介绍了-combinational选项的使用,为了更好理解,这里举一个具体例子。下图中,上级时钟同时传递到寄存器和多路选择器中,寄存器对时钟进行2分频。多路选择器从寄存器的2分频时钟和上级时钟中选择一个作为生成时钟输出。
显而易见,从上级时钟到生成时钟有两条路径,一条为时序路径,一条为组合路径。如果我们只希望考虑组合路径上的延迟时,定义生成时钟时就需要使用-combinational选项。
这种类型时钟算是生成时钟的一种特例,“自动”是指在已经定义了上级时钟的情况下,Vivado会自动为时钟管理单元CMBs(Clock Modifying Blocks)的输出管脚创建约束。官方称作Automatically Derived Clocks或Auto-generated Clock。
7系列FPGA的CMB单元包括MMCM、PLL、BUFR、PHASER;UltraScale系列FPGA的CMB单元种类与数量更多,这里不陈列。如果约束中已经存在用户在某一网表对象上定义的时钟,则不会创建相同对象上的自动生成时钟。
下面给出一个具体例子。下图中上级时钟clkin驱动clkip/mmcm0单元的CLKIN输入,该单元是一个MMCME2资源的实例。则自动生成时钟的定义源点为clkip/mmcm0/CLKOUT,顶层与此源点连接的网络名为clkip/cpuClk,自动生成时钟的名字便是cpuClk。
如上所述,Vivado会自动创建自动生成时钟的名称(Name),如果两个名称发生冲突也会自动添加后缀,如usrclk、usrclk_1等等。Vivado也支持对已经创建好的自动生成时钟重新命名,但很少用到,这里不做介绍。
很多初学者应该也没有接触过时钟组这个概念。默认情况下,Vivado会测量设计中所有时钟之间的路径时序。添加如下两种约束可以控制该功能:
划分时钟组通常有两个依据:(1).原理图或时钟网络报告中的时钟树拓扑图,判断哪些时钟不应该放在一起做时序分析;(2).时钟交互报告查看两个时钟间存在的约束,判断它们是否有共享的主时钟(代表是否有已知的相位关系)或者是否有公共周期。
但要明白,我们设定时钟组的目的还是为了保证设计在硬件中能正常工作,因此我们必须确保这些忽略了时序分析的路径有合适的再同步电路或异步数据传输协议。根据时钟间的关系,可以做如下分类:
同步时钟可以安全地进行时序分析。异步时钟和不可扩展时钟虽然通过时序分析也会得到一个裕量值,但这个值不可作为可靠结果。从这个角度出发,不可扩展时钟也可以视作一种特殊的异步时钟。这就需要通过设置时钟组来忽略异步时钟的时序路径上的时序分析。
这里举个例子,一个主时钟clk0通过MMCM生成两个时钟usrclk和itfclk;另一个主时钟clk1通过另一个MMCM生成两个时钟clkrx和clktx。用如下命令创建异步时钟组:
set_clock_groups -name async_clk0_clk1 -asynchronous -group {clk0 usrclk itfclk} -group {clk1 clkrx clktx}
#如果时钟名称事先不知道,可以用如下写法
set_clock_groups -name async_clk0_clk1 -asynchronous -group [get_clocks -include_generated_clocks clk0] -group [get_clocks -include_generated_clocks clk1]
下面再介绍另一种会用到时钟组的情况。某些设计会有几个操作模式,不同操作模式使用不同的时钟。这些时钟通常由专用的时钟选择器进行选择,如BUFGMUX和BUFGCTRL,最好不要用LUT作时钟选择器。
这些单元都是组合逻辑单元,Vivado会将所有输入传递到输出。在Vivado IDE中,几个时序时钟可以同时存在时钟树上,方便地同时报告所有操作模式。但是在硬件中这是不可能的,它们之间是互斥的,这些时钟便称作互斥时钟。
举个例子,一个MMCM实例生成的两个时钟clk0和clk1,与一BUFGMUX实例clkmux相连,clkmux的输出驱动设计时钟树。默认情况下,虽然clk0和clk1共享同一时钟树,且不能同时存在,Vivado还是会分析clk0和clk1之间的路径。这个问题要通过设置互斥时钟组来解决,达到禁止分析这两个时钟间路径 的目的。约束如下:
set_clock_groups -name exclusive_clk0_clk1 -physically_exclusive -group clk0 -group clk1
在ASIC工艺中使用-physically_exclusive和-logically_exclusive代表不同的信号完整性分析模式,但对于Xilinx FPGA而言,二者是等价的,都可以使用。
本文的上述约束可以说都是对时钟的理想特征进行约束,为了更精确地进行时序分析,设计者还必须设定一些与运行环境相关的可预测变量和随机变量。这部分也称作时钟的不确定性特征。
经过板子上和FPGA器件内部的传输,时钟边沿到达目的地后会有一个确定的延迟。这个延迟可以分为两个部分看待:
下面给出一个约束源端时钟延迟的例子:
#设定最小源端延迟值
set_clock_latency -source -early 0.2 [get_clocks sysclk]
#设定最大源端延迟值
set_clock_latency -source -late 0.5 [get_clocks sysclk]
对于ASIC器件来说,时钟抖动通常代表了时钟不确定性特征;但对于Xilinx FPGA而言,抖动属性被当作可预测变量看待。抖动有的需要单独设置,有的在时序分析过程中自动计算。抖动分为两种:
下面给出一个约束输入抖动的例子:
#主时钟传输过程中有±100ps的抖动
set_input_jitter [get_clocks -of_objects [get_clocks sysclk]] 0.1
不过,时钟抖动对整个时钟不确定性计算的影响不是太大。计算时钟不确定性时对每条路径都是独立的,且主要依赖于时钟拓扑结构、路径上的时钟对、时钟树上是否存在MMCM/PLL单元等其它因素。
使用set_clock_uncertainty命令可以根据需要为特定的时钟关系定义附加的时钟不确定性,这样在时序分析时,可以为设计中的某些部分增加额外裕量。
前面文章说过XDC约束带有顺序性,后面的约束会重写前面的约束。但在这里,时钟间的不确定性总是优先于单个时钟的不确定性,不管约束顺序如何。看下面的例子:
set_clock_uncertainty 2.0 -from [get_clocks clk1] -to [get_clocks clk2]
set_clock_uncertainty 1.0 [get_clocks clk1]
这里首先约束从clk1到clk2有一个2ns的时钟不确定性,接着又约束clk1有1ns的时钟不确定性,但是后面这条约束不会改动从clk1到clk2之间的关系。