3、SDC约束入门:时钟定义(create_clock)、生成时钟(create_generated_clock)、时钟组(set_clock_groups)
时钟,是芯片设计的心脏跳动。没有时钟,整个芯片就是一滩死水。做时序分析,第一步就是把时钟定义清楚。我见过太多新手,上来就写约束,结果时钟没搞对,后面全白忙活。
今天咱们就聊聊SDC里最核心的三个时钟命令:create_clock、create_generated_clock、set_clock_groups。这三个命令,你玩明白了,时序约束就入门了。
3.1 时钟定义:create_clock
create_clock是SDC里最基础的命令。它的作用很简单——告诉工具,哪个端口或节点是时钟源,频率是多少,占空比什么样。
语法长这样:
create_clock -name <时钟名> -period <周期> [ -waveform <上升沿 下降沿> ] [ -add ] <目标端口/节点>
举个例子:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这条命令的意思是:在端口clk_in上定义一个叫clk_sys的时钟,周期10ns(也就是100MHz)。默认占空比50%,上升沿在0ns,下降沿在5ns。
重点来了:如果你不指定-waveform,工具默认上升沿在0时刻,下降沿在周期的一半。但实际项目中,我建议你养成好习惯,显式写出来:
create_clock -name clk_sys -period 10.0 -waveform {0 5} [get_ports clk_in]
这样一眼就能看出时钟的边沿位置,避免后续分析时搞混。
还有一个容易被忽略的参数——-add。同一个端口上可以定义多个时钟吗?可以。但如果你第二次定义时不加-add,工具会覆盖掉之前的定义。我在项目中遇到过,有人在一个PLL输出端口上定义了两次时钟,忘了加-add,结果第一个时钟被覆盖了,后端的时序分析全乱套。
我的习惯:只要在同一个端口上定义多个时钟,一定加上-add。哪怕当前只有一个时钟,也写上,防止以后改代码时忘记。
3.2 生成时钟:create_generated_clock
芯片里很少有直接用一个原始时钟的。大部分情况下,我们会用PLL分频、倍频,或者用门控时钟、多路选择器产生新的时钟。这些衍生出来的时钟,就叫生成时钟。
create_generated_clock就是用来定义这些衍生时钟的。它的核心思想是:告诉工具,这个新时钟和源时钟之间是什么关系。
语法:
create_generated_clock -name <时钟名> -source <源时钟端口/节点> -divide_by <分频系数> -multiply_by <倍频系数> [ -edges <边沿列表> ] [ -combinational ] <目标节点>
看个实际例子:
create_generated_clock -name clk_div2 -source [get_ports clk_in] -divide_by 2 [get_pins U_PLL/clk_out]
这条命令定义了一个二分频时钟,源时钟是clk_in,生成时钟在U_PLL/clk_out这个引脚上。
嗯,这里要注意:-source指定的是源时钟的定义点,不是源时钟的名字。很多人搞混,写成-source [get_clocks clk_sys],这样工具会报错。正确的写法是-source [get_ports clk_in]或者-source [get_pins ...]。
我曾经踩过的坑:有一次,我定义了一个生成时钟,但忘了指定-master_clock参数。结果工具自动匹配了源时钟,但匹配错了——因为那个节点上有两个时钟定义。从那以后,我只要遇到多时钟场景,一定显式指定-master_clock:
create_generated_clock -name clk_div2 -source [get_ports clk_in] -master_clock clk_sys -divide_by 2 [get_pins U_PLL/clk_out]
这样就不会有歧义了。
生成时钟还有一种特殊场景——组合逻辑产生的时钟。比如用AND门或者OR门把两个时钟合在一起。这时候要用-combinational参数:
create_generated_clock -name clk_gated -source [get_ports clk_in] -combinational [get_pins U_GATE/clk_out]
说白了,-combinational就是告诉工具:这个生成时钟和源时钟之间没有固定的分频倍频关系,你按组合逻辑传播去分析就行。
3.3 时钟组:set_clock_groups
一个芯片里往往有几十个时钟。有些时钟之间是同步的,需要做时序检查;有些是异步的,不需要检查。如果让工具对所有时钟对都做分析,那运行时间会爆炸,而且会产生大量假路径。
set_clock_groups就是用来告诉工具:哪些时钟之间不需要做时序分析。
语法:
set_clock_groups -asynchronous -group <时钟列表1> -group <时钟列表2> [ -group <时钟列表3> ... ]
举个例子:
set_clock_groups -asynchronous \
-group {clk_sys clk_div2} \
-group {clk_usb clk_eth}
这条命令的意思是:clk_sys和clk_div2是一组,clk_usb和clk_eth是另一组。两组之间是异步的,不需要做时序检查。但组内的时钟之间,还是要做分析的。
注意:-asynchronous和-logically_exclusive、-physically_exclusive的区别:
- -asynchronous:时钟之间是异步的,没有固定的相位关系。比如两个独立的PLL输出。
- -logically_exclusive:时钟在逻辑上不会同时出现。比如MUX选择出来的两个时钟,同一时刻只有一个有效。
- -physically_exclusive:时钟在物理上不会同时存在。比如测试模式和功能模式下的时钟。
你想想看,如果搞混了这些参数,工具可能会漏掉一些关键的时序路径,或者多出大量没用的分析。我建议你:
- 异步时钟用
-asynchronous - MUX选择的时钟用
-logically_exclusive - 测试/功能模式切换用
-physically_exclusive
我的经验:写set_clock_groups时,尽量把相关的时钟放在同一个-group里。比如所有CPU相关的时钟放一组,所有外设相关的时钟放另一组。这样代码可读性强,也方便后续维护。我曾经接手过一个项目,前一个人把所有时钟都放在一个-group里,然后用了十几个-group,看得我头皮发麻。
3.4 三个命令的配合使用
实际项目中,这三个命令往往是配合使用的。我画一个典型的流程:
- 先用
create_clock定义所有原始时钟(比如晶振输入、PLL参考时钟) - 再用
create_generated_clock定义所有衍生时钟(分频、倍频、门控时钟) - 最后用
set_clock_groups声明时钟之间的异步/互斥关系
举个例子,一个典型的SoC时钟约束:
# 步骤1:定义原始时钟
create_clock -name clk_osc -period 20.0 -waveform {0 10} [get_ports osc_in]
create_clock -name clk_usb_ref -period 8.33 -waveform {0 4.165} [get_ports usb_ref]
# 步骤2:定义生成时钟
create_generated_clock -name clk_cpu -source [get_ports osc_in] -master_clock clk_osc -multiply_by 10 -divide_by 1 [get_pins U_PLL/cpu_clk]
create_generated_clock -name clk_cpu_div2 -source [get_pins U_PLL/cpu_clk] -master_clock clk_cpu -divide_by 2 [get_pins U_DIV/clk_out]
create_generated_clock -name clk_usb_48m -source [get_ports usb_ref] -master_clock clk_usb_ref -divide_by 1 [get_pins U_USB_PLL/clk_out]
# 步骤3:声明时钟组
set_clock_groups -asynchronous \
-group {clk_osc clk_cpu clk_cpu_div2} \
-group {clk_usb_ref clk_usb_48m}
你看,这样写出来,整个时钟结构一目了然。工具也能高效地做时序分析,不会在异步路径上浪费时间。
最后提醒一句:写完时钟约束后,一定要用report_clock命令检查一下。看看时钟定义是否正确,有没有遗漏。我见过有人写了20个create_generated_clock,结果漏了一个,导致那条路径的时序完全没被检查。流片回来才发现,那叫一个惨。
好了,时钟定义这块就聊到这儿。下一章咱们聊聊输入输出延迟约束,那是把时钟和外部接口连起来的关键一步。