4. 生成时钟与虚拟时钟:create_generated_clock、虚拟时钟的概念与应用场景
时钟,是数字电路的“心跳”。但一颗芯片里,不可能只有一个原始时钟在跑。你想想看,PLL锁相环出来一个100MHz的主时钟,然后经过分频、倍频、门控,会衍生出各种频率、相位各异的时钟。这些衍生出来的时钟,就是我们要聊的生成时钟。
至于虚拟时钟,嗯,它有点特殊。它不挂在任何实际的物理端口或管脚上,纯粹是STA分析时“凭空造出来”的一个参考点。听起来有点玄乎?别急,我们一个一个说。
4.1 生成时钟:create_generated_clock
生成时钟,说白了就是由主时钟(master clock)经过某种变换得到的时钟。常见的变换方式有:
- 分频:比如主时钟100MHz,分频后得到50MHz
- 倍频:比如PLL内部倍频到200MHz
- 门控:用使能信号控制时钟的开关
- 相位调整:比如产生90度相移的时钟
在SDC约束里,我们用 create_generated_clock 来定义这些衍生时钟。它的基本语法是:
create_generated_clock -name <clock_name> \
-source <master_clock_pin> \
-divide_by <factor> \
[get_pins <output_pin>]
举个例子,假设我们有一个二分频电路,输入是主时钟 clk_in,输出是 clk_div2:
create_clock -name CLK_MAIN -period 10 [get_ports clk_in]
create_generated_clock -name CLK_DIV2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins div_inst/Q]
这里有个细节我特别想强调:-source 指定的是主时钟的源端,而不是生成时钟的输入端。很多新手会搞混,把 -source 指向分频器的输入引脚,这是不对的。我个人习惯把 -source 指向主时钟的 get_ports 或 get_pins,这样更清晰。
核心要点:生成时钟的 -source 必须指向主时钟的定义点,而不是生成时钟的输入引脚。否则STA工具无法正确计算时钟之间的相位关系。
4.2 生成时钟的常见场景
我在项目中遇到过几种典型的生成时钟场景,分享给你:
场景一:整数分频
最常用的就是二分频、四分频。比如:
create_generated_clock -name CLK_DIV4 \
-source [get_ports clk_in] \
-divide_by 4 \
[get_pins div4_inst/Q]
场景二:带相位的生成时钟
有时候我们需要产生一个与主时钟有固定相位差的时钟。比如90度相移:
create_generated_clock -name CLK_90 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {0 2.5 0} \
[get_pins ph_shift_inst/Q]
这里的 -edges 和 -edge_shift 可以精确控制每个时钟沿的位置。说实话,这个参数我一开始也觉得绕,后来画了个波形图才彻底搞明白。
场景三:门控时钟
门控时钟在低功耗设计中很常见。比如:
create_generated_clock -name CLK_GATED \
-source [get_ports clk_in] \
-combinational \
[get_pins and_gate_inst/Y]
用 -combinational 告诉工具:这个生成时钟是组合逻辑产生的,没有经过时序单元。
我的经验:对于门控时钟,我建议同时使用 set_clock_gating_check 来约束门控信号的建立/保持时间。否则,门控信号在时钟沿附近跳变时,可能会产生毛刺。我曾经因为这个吃过亏,后来就养成了这个习惯。
4.3 虚拟时钟:概念与用途
虚拟时钟,英文叫 virtual clock。它不绑定任何物理端口或管脚,纯粹是STA分析时的一个“假想时钟”。
你可能会问:为什么要造一个不存在的时钟?
原因很简单:有些路径的时序分析,需要一个参考时钟,但这个时钟在芯片内部并不存在。
最常见的场景是IO接口时序约束。比如,芯片的输入数据来自外部器件,外部器件有自己的时钟。这个外部时钟不会进入我们的芯片,但我们需要用它来约束输入数据的到达时间。
虚拟时钟的定义方式:
create_clock -name VCLK -period 10
注意,这里用的是 create_clock,不是 create_generated_clock。而且没有指定 get_ports 或 get_pins,所以它是个虚拟时钟。
4.4 虚拟时钟的应用场景
我总结了一下,虚拟时钟主要有三个用途:
| 应用场景 | 说明 | 示例 |
|---|---|---|
| 输入延迟约束 | 外部器件发送数据到芯片,用外部时钟作为参考 | set_input_delay -clock VCLK ... |
| 输出延迟约束 | 芯片发送数据到外部器件,用外部时钟作为参考 | set_output_delay -clock VCLK ... |
| 跨时钟域分析 | 两个异步时钟域之间需要虚拟时钟做中间参考 | 配合 set_false_path 使用 |
举个例子,假设外部器件以100MHz的时钟向我们的芯片发送数据,数据在时钟上升沿之后2ns到达芯片引脚:
# 定义虚拟时钟,模拟外部器件的时钟
create_clock -name EXT_CLK -period 10
# 约束输入延迟
set_input_delay -clock EXT_CLK -max 2.0 [get_ports data_in]
set_input_delay -clock EXT_CLK -min 0.5 [get_ports data_in]
这样,STA工具就知道:数据在EXT_CLK上升沿之后0.5~2.0ns之间到达芯片引脚。然后工具会检查芯片内部能否在下一个时钟沿之前捕获到这些数据。
注意:虚拟时钟的周期和波形,必须与外部器件的实际时钟一致。如果外部器件是DDR接口(双沿采样),虚拟时钟也要定义成相应的双边沿波形。我曾经见过有人把虚拟时钟周期设错了,导致整个IO时序分析全乱套。
4.5 生成时钟与虚拟时钟的关系
为了让你更直观地理解这两个概念,我画了一张图:
从这张图可以看得很清楚:
- 生成时钟是主时钟的“孩子”,它存在于芯片内部,有实际的物理节点(比如分频器的输出引脚)
- 虚拟时钟是“凭空造出来”的,它没有物理节点,只存在于STA工具的“脑海”里
4.6 避坑指南
最后,分享几个我踩过的坑:
我曾经犯过的错误:
- 生成时钟的-source指向错误:把-source指向了分频器的输入引脚,而不是主时钟的源端。结果STA工具报了一堆奇怪的时序违例,查了半天才发现是这里错了。
- 虚拟时钟忘记定义:在set_input_delay里引用了一个虚拟时钟,但忘了在前面用create_clock定义它。工具直接报错,说找不到这个时钟。
- 生成时钟的波形定义不完整:对于非50%占空比的时钟,没有用-edges和-edge_shift精确描述波形。导致工具默认用了50%占空比,时序分析结果与实际不符。
我的建议:
- 写SDC时,先把所有主时钟和虚拟时钟定义好,再定义生成时钟。顺序很重要。
- 对于复杂的生成时钟(比如带相位偏移的),建议先画波形图,再写约束。画图比空想要靠谱得多。
- 用
report_clock命令检查所有时钟的定义是否正确。我每次写完SDC都会跑一遍这个命令,确认无误后再往下走。
好了,生成时钟和虚拟时钟就聊到这里。这两个概念是时序约束的基础,搞懂了它们,后面的IO约束和跨时钟域分析就会轻松很多。