4. 时钟定义:create_clock 的四种方法与参数设置
做 HS400 接口时序约束,第一步就是先把时钟弄对。时钟定义错了,后面所有分析都是白搭。我刚开始做 DDR 的时候,就吃过这个亏 —— 一个时钟周期设错,整条链路时序全不对。
市面上常见的教程会告诉你 create_clock 有几种写法,但很少讲清楚每种写法到底用在什么场景。今天咱们就把它掰开了聊。
4.1 create_clock 的四种方法
我习惯把时钟定义分成四类:主时钟、虚拟时钟、生成时钟、多周期时钟。其实严格来说,create_clock 指令本身只有一种语法,但它可以用在不同的上下文里,产生不同的效果。
| 方法 | 典型用法 | 适用场景 |
|---|---|---|
| 主时钟 (Master Clock) | 在输入端口或时钟引脚上定义 | 外部晶振、PLL 参考时钟输入 |
| 虚拟时钟 (Virtual Clock) | 只定义周期,不绑定到任何引脚 | 片外接口时序、多芯片同步分析 |
| 生成时钟 (Generated Clock) | 由主时钟通过分频/倍频/相移产生 | 内部 PLL 输出、时钟门控后的时钟 |
| 多周期时钟约束 | 配合 set_multicycle_path 使用 | 跨时钟域或慢速数据路径的时序例外 |
你想想看,前三种才是真正的“时钟定义”,多周期其实属于时序例外。但我把它列进来是因为新手很容易把“多周期时钟”这个概念和 create_clock 混淆。
4.1.1 主时钟定义
最基础的形式。在时钟输入端口或 clock pin 上定义,給出周期和占空比。
create_clock -name sys_clk -period 10.0 [get_ports clk_in]
注意:如果时钟通过 PLL 后再输出,那 PLL 输出端要用 create_generated_clock,别再用 create_clock 覆盖上去。我见过有人两个都写,结果时序分析工具直接报 conflict。
4.1.2 虚拟时钟
这个很多新手会漏掉。虚拟时钟不挂载在任何物理节点上,它只用于描述外部器件的工作时钟。比如 HS400 接口对端芯片的时钟。
create_clock -name vclk -period 5.0
注意这里没有指定 -source 或 get_ports。虚拟时钟通常用来做 input/output delay 的参考边沿。我曾经遇到过一个案例:输出延时约束用了内部时钟的 rise/fall,结果综合工具算出来的 setup slack 差了 200ps,就是因为没单独定义虚拟时钟。
4.1.3 生成时钟
HS400 内部常用 PLL 产生 1.25GHz 的 DDR 时钟。生成时钟必须指定源时钟和分频/倍频关系。
create_generated_clock -name ddr_clk -source [get_pins pll/clkin] \
-divide_by 2 -multiply_by 5 [get_pins pll/clkout]
这里有个坑:-source 必须指定源时钟的 pin 或 port,不能直接写时钟名。工具需要知道生成时钟的相位关系。
4.1.4 多周期约束
严格说这不是 create_clock,但很多人把它和时钟定义搞混。当数据路径需要多个时钟周期才能稳定时,用 set_multicycle_path 调整检查边沿。
set_multicycle_path -setup 2 -from [get_clocks clk_a] -to [get_clocks clk_b]
我建议只有在充分理解时序分析机制后才用这个,新手乱设多周期路径容易把错误的起点当成正确。
4.2 时钟周期、占空比与抖动参数设置
定义完时钟结构,接下来就是设置具体的参数。HS400 对时钟质量要求很高,尤其是抖动。
4.2.1 时钟周期(period)
周期单位默认 ns。HS400 在 1.25GHz 时周期是 800ps。注意 -period 值必须是上升沿到下一个上升沿的时间,对于 DDR 总线,我们真正关心的是半周期(400ps)。
4.2.2 占空比(duty cycle)
默认 50%,但实际 PLL 输出可能有偏差。可以用 -waveform 精确指定边沿位置:
create_clock -name hs_clk -period 0.8 -waveform {0.0 0.38} [get_ports hclk]
这个例子中高电平持续 380ps,占空比 47.5%。为什么要设成 0.38 而不是 0.4?因为 PLL 实测就是 380ps,写真实值才能让时序分析更准。
| 参数 | 含义 | 单位 | HS400 典型值 |
|---|---|---|---|
| period | 时钟周期 | ns / ps | 0.8 ns (800 ps) |
| -waveform rise | 上升沿时刻 | ns / ps | 0.0 |
| -waveform fall | 下降沿时刻 | ns / ps | 0.38 (占空比 ~47.5%) |
4.2.3 时钟抖动(clock jitter)
抖动直接影响时序裕量。SDC 里用 set_clock_uncertainty 来建模抖动,而不是在 create_clock 里设。不过我经常看到有人把抖动写在 create_clock 的注释里,然后忘了设 uncertainty。
set_clock_uncertainty -setup 0.05 -hold 0.05 [get_clocks hs_clk]
这个值需要从芯片 datasheet 或 PLL 仿真报告中拿。你想想看,如果抖动量设小了,流片后可能会因为实际抖动大导致 setup 违例;设大了又过度悲观,浪费面积。
4.3 常见错误与避坑指南
我总结几个实战中容易犯的错:
- 虚拟时钟命名冲突: 虚拟时钟名称如果和真实时钟相同,工具会认为你在重新定义时钟。我习惯虚拟时钟名称加
_v后缀。 - 生成时钟的 -source 写错: 我曾经把 PLL 的输出 pin 当作 source,结果工具找不到源时钟。必须指向 PLL 的输入 pin(通常是 clkin)。
- 没有定义虚拟时钟就做 input delay: 如果不定义虚拟时钟,工具会以内部时钟的边沿作为参考,导致 output delay 的参考点与实际对端不一致。
- 周期单位混淆: 有的团队用 ps,有的用 ns。我建议统一用 ns 并保留三位小数,比如
0.800,避免小数点位数丢失。
好了,这一节把 create_clock 的四种方法和参数设置都理清了。真正写约束时,我会把主时钟和虚拟时钟先写进 SDC 模板,生成时钟根据实际时钟树补全,最后再统一设 uncertainty。这样流程走下来,很少因为定义漏了而导致时序分析错误。