4. 时钟定义与生成(create_clock、create_generated_clock)

时钟,是数字设计的“心跳”。没有它,所有寄存器都像没睡醒一样。我刚开始做DDR接口时,觉得时钟定义最简单,不就是告诉工具“这里有个时钟,频率多少”吗?结果第一次跑时序分析,发现DDR数据眼图全乱套……后来才明白,时钟定义错了,后面所有约束都是白费功夫。

4.1 基础时钟定义:create_clock

create_clock 是时序约束里最基础的命令。它的作用很简单——告诉STA工具:“看,这根线上有个时钟,周期是X,波形长这样。”

基本语法:

create_clock -name <时钟名> -period <周期> [ -waveform <上升沿时间 下降沿时间> ] [ -add ] <端口或管脚>

举个例子,我们给DDR控制器主时钟 clk_200m 定义200MHz时钟,50%占空比:

create_clock -name clk_200m -period 5.0 -waveform {0 2.5} [get_ports clk_in]

-waveform 默认值是 {0 period/2},所以50%占空比可以不写。但我个人习惯总是显式写出-waveform,尤其是设计中有非50%占空比时钟时——例如DDR内存的DQS时钟,占空比可能45%/55%,不写清楚工具会按50%算,你想想看,那偏差可不止一点点。

参数说明我踩过的坑
-name时钟名,可与其他定义名相同(需配合-add)同名不加-add会被覆盖
-period周期,单位ns或ps(取决于库设定)单位搞混,差10倍都有可能
-waveform上升沿/下降沿时刻列表只写了一个值会报错
-add允许在同一节点定义多个时钟忘了加,后定义的把前一个冲掉

小技巧:如果时钟源是差分对(比如DDR的CK/CK#),你只需要在 P 端定义时钟,工具会自动处理差分关系。不用两边都定义——我曾经在 CK_N 上也加了一个create_clock,结果分析出两条完全相反的时钟路径,浪费半天排错。

4.2 生成时钟:create_generated_clock

在DDR系统中,大多数内部时钟都不是直接从管脚进来的,而是通过PLL、分频器、门控单元等产生的。你不能再用 create_clock 去定义它们,因为那会割裂时钟间的关系。正确的做法是用 create_generated_clock。

为什么?因为生成时钟需要告诉工具:这个时钟与源时钟的频率比、相位关系、分频还是倍频。只有这样,跨时钟域的时序路径才能被正确分析——尤其是DDR控制器内部的写数据路径和读数据路径,往往使用不同相位的生成时钟。

基本语法:

create_generated_clock -name <名称> -source <源时钟管脚/端口> \
    -divide_by <分频系数> | -multiply_by <倍频系数> \
    -master_clock <源时钟名> [ -edges <边沿列表> ] [ -edge_shift <偏移列表> ] <生成时钟输出管脚>

最常用的场景:DDR的二分频时钟。比如PLL输出800MHz,内部逻辑需要400MHz:

create_generated_clock -name clk_400m -source [get_pins pll/clk_out] -divide_by 2 [get_pins divider/clk_out]

嗯,这里要注意:-source 必须指定是源时钟经过的管脚或端口,不是源时钟的名字。我见过新手直接写 -source [get_clocks clk_800m],工具会报错——-source 要的是物理节点,时钟对象不行。

4.3 复杂相位生成:使用 -edges 和 -edge_shift

DDR中经常需要产生不同相位的时钟,用来做数据选通。比如从PLL输出一个0°相位和一个90°相位的时钟:

# 定义源时钟
create_clock -name pll_out -period 2.5 [get_pins pll/clk_out]

# 0°相位输出
create_generated_clock -name clk_0deg -source [get_pins pll/clk_out] \
    -edges {1 3 5} -edge_shift {0 0 0} [get_pins phy_clk_0]

# 90°相位输出(上升沿偏移0.625ns)
create_generated_clock -name clk_90deg -source [get_pins pll/clk_out] \
    -edges {1 3 5} -edge_shift {0.625 0 0.625} [get_pins phy_clk_90]

-edges 指定源时钟的第几个边沿作为生成时钟的边沿,-edge_shift 加上额外偏移。说白了,这是“手算”生成时钟波形的底层方法,对于复杂的分数倍频或非整数相位很有用。我记得在写DDR3的写均衡训练约束时,必须用这个来对齐DQS和CK的相位,当时花了一整天调试边沿定义——后来发现少写了一个偏移值。

4.4 区分 create_clock 和 create_generated_clock

特性create_clockcreate_generated_clock
适用场景芯片管脚输入时钟、PLL等效时钟源内部PLL输出、分频/倍频/门控时钟
父子关系独立时钟,无父时钟必须有父时钟(-source)
频率关系直接指定周期通过divide_by/multiply_by计算
相位关系waveform定义时间edges+edge_shift相对源时钟
自动传播沿时钟网络传播不自动传播到扇出(需手动定义)

警告:不要在生成时钟的扇出分叉点上重复定义 create_generated_clock。如果同一个生成时钟信号又经过了一个缓冲器,你应该让它自动传播,而不是再给它加一个“生成时钟”定义。我曾在DDR的写PLL输出到PHY的路径上,每个缓冲输出都写了一个生成时钟,结果STA报出几十个互相矛盾的时钟域……全线红色。

4.5 时钟定义的整体流程

来看一张图,帮助你理解时钟定义的层次:

DDR 时钟定义层次结构 芯片输入时钟 (clk_in) create_clock -name sys_clk -period 5.0 PLL 输出 (pll_clk) create_generated_clock -source [get_ports clk_in] 分频时钟 (div2_clk) create_generated_clock -divide_by 2 90° 相移时钟 (shift90) create_generated_clock -edge_shift 0.625 DDR PHY 写数据路径 DDR PHY 读数据路径 注:每个生成时钟都必须显式定义,工具不会自动从PLL属性和分频因子推断出正确的约束

从图上能看到:顶层用 create_clock 定义输入时钟,然后内部PLL、分频器、相移网络都要用 create_generated_clock 而且必须一一定义。工具不会因为你在PLL上设了倍频因子就自动知道内部时钟长什么样——你想想看,万一分频逻辑里加了可编程分频比,工具怎么猜?

4.6 实战避坑清单

  • 不要用 create_clock 覆盖内部生成时钟——这会切断时钟间的相位关系,导致跨时钟路径的setup/hold分析错误。我见过有人图省事,在PLL输出端口直接写create_clock,结果DDR的读选通信号和主时钟之间时序完全不收敛。
  • 生成时钟的 -source 必须选择源时钟经过的物理管脚,不是端口的最末端。比如PLL输出到分频器,source应该选分频器的输入管脚,而不是分频器输出。
  • 多生成时钟共用一个源节点时,要用 -master_clock 区分——例如一个PLL输出多个频率的时钟,每个生成时钟要指明来自哪个主时钟。我在DDR3 ECC设计中,一个PLL输出5种时钟,不写master_clock的话工具会随机匹配,时序报告完全看不懂。
  • 门控时钟(如时钟使能)不要用create_generated_clock,建议用set_clock_gating_check配合。曾经有同事在门控时钟输出加了生成时钟定义,结果综合工具把它当成独立时钟源,优化掉了门控逻辑——流片回来时钟一直处于使能状态,功耗翻倍。

4.7 检查时钟定义是否完整

写完约束后,别急着跑STA。先用 report_clock -skew 和 report_generated_clocks 看看工具理解了什么。

我习惯写一个快速检查脚本:

# 列出所有时钟
report_clock -groups

# 检查生成时钟与源时钟的频率比是否正确
report_generated_clocks -verbose

# 特别关注是否有未定义的传播时钟
all_clocks -unused_clock_pins

如果某个内部节点出现了 unspecified clock 的警告,多半是你漏了某个生成时钟定义。这时候要回溯时钟路径,找到那个分叉点,补上 create_generated_clock。

核心原则:所有经过PLL、分频器、相移单元产生的时钟都要显式定义。宁可多写一个,也别漏掉一个。漏定义会导致工具用虚假的“自动传播”时钟来估算时序——那些数字跟实际芯片行为差十万八千里。

好了,关于时钟定义,我们就说这么多。这些命令是时序约束的地基,一定要打扎实。下个知识点我们会讨论输入输出延迟的约束——那是DDR时序分析的真正战场。