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 / ps0.8 ns (800 ps)
-waveform rise上升沿时刻ns / ps0.0
-waveform fall下降沿时刻ns / ps0.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 违例;设大了又过度悲观,浪费面积。

警告:抖动值不能直接套用 create_clock 的 -jitter 选项(有些工具支持,但非标准)。统一用 set_clock_uncertainty,这样工具能正确区分 system jitter 和 random jitter。

4.3 常见错误与避坑指南

我总结几个实战中容易犯的错:

  • 虚拟时钟命名冲突: 虚拟时钟名称如果和真实时钟相同,工具会认为你在重新定义时钟。我习惯虚拟时钟名称加 _v 后缀。
  • 生成时钟的 -source 写错: 我曾经把 PLL 的输出 pin 当作 source,结果工具找不到源时钟。必须指向 PLL 的输入 pin(通常是 clkin)。
  • 没有定义虚拟时钟就做 input delay: 如果不定义虚拟时钟,工具会以内部时钟的边沿作为参考,导致 output delay 的参考点与实际对端不一致。
  • 周期单位混淆: 有的团队用 ps,有的用 ns。我建议统一用 ns 并保留三位小数,比如 0.800,避免小数点位数丢失。
个人习惯:每次写约束之前,先画一张简单的时钟树草图——哪些是主时钟,哪些是生成时钟,物理连接路径是什么。画完再写 SDC,不容易漏。
时钟定义四种方法结构图 时钟源 主时钟 直接定义在 `get_ports` 虚拟时钟 不指定端口,只定义周期 生成时钟 需指定 source 与分频关系 时钟参数设置 period | -waveform (占空比) | set_clock_uncertainty (抖动) 命名规范、单位统一、避免冲突 四种方法必须配合使用,不可遗漏

好了,这一节把 create_clock 的四种方法和参数设置都理清了。真正写约束时,我会把主时钟和虚拟时钟先写进 SDC 模板,生成时钟根据实际时钟树补全,最后再统一设 uncertainty。这样流程走下来,很少因为定义漏了而导致时序分析错误。