2. 时钟约束入门

时钟,是数字电路的“心跳”。

做时序约束,第一件事就是把时钟定义清楚。我见过太多新手,上来就写 create_clock,结果频率写错、占空比写反、源定义搞混——最后STA跑出来一堆violation,还不知道问题出在哪。

今天咱们就把这个命令掰开揉碎了讲。你跟着我走一遍,以后不会再在这上面栽跟头。

1. create_clock 命令长什么样?

先看最基础的语法:

create_clock -name <时钟名> -period <周期> -waveform {<上升沿时间> <下降沿时间>} [<源对象>]

嗯,就这么简单。但参数里全是坑。

我举个例子:

create_clock -name clk_sys -period 10.0 -waveform {0 5} [get_ports clk_in]

这条命令的意思是:在端口 clk_in 上定义一个名为 clk_sys 的时钟,周期10ns,上升沿在0ns,下降沿在5ns——也就是50%占空比。

你可能会问:“-waveform 不写行不行?”

行。默认就是50%占空比,上升沿在0,下降沿在周期的一半。但我不建议你省略。为什么?

因为一旦你省略,别人看你的约束脚本,还得猜你的占空比意图。代码是写给人看的,清晰第一。

核心要点: create_clock 的四个要素——名字、周期、波形、源。缺一不可,但源可以省略(后面会讲)。

2. 时钟周期与占空比——别想当然

周期好理解,就是时钟重复的时间间隔。单位默认是纳秒(ns)。

但占空比,很多人理解有偏差。

占空比 = 高电平时间 / 周期 × 100%。

在 -waveform 里,第一个数是上升沿时间,第二个数是下降沿时间。所以:

  • {0 5} 周期10ns → 高电平5ns → 占空比50%
  • {0 3} 周期10ns → 高电平3ns → 占空比30%
  • {2 7} 周期10ns → 高电平5ns → 占空比50%(但相位偏移了2ns)

我遇到过一种情况:有人写 {0 4} 周期8ns,以为占空比50%。结果一算,高电平4ns,周期8ns,确实是50%。但问题出在哪?

他忘了——下降沿必须在上升沿之后,且不能超过周期。如果写 {0 6} 周期8ns,下降沿在6ns,高电平6ns,占空比75%。这没问题。但如果你写 {0 9} 周期8ns,工具会报错,因为下降沿超出了周期范围。

避坑指南: 我曾经在项目里看到有人把 -waveform {0 5} 写成了 -waveform {5 0},结果工具把上升沿和下降沿搞反了,时序分析全乱套。记住:第一个是上升沿,第二个是下降沿。

3. 时钟源定义——到底绑在哪里?

时钟源,就是 create_clock 命令最后那个参数。它告诉工具:这个时钟信号从哪个物理点进入芯片。

常见的源有:

  • 输入端口:[get_ports clk_in]
  • 内部节点:[get_pins u_pll/clk_out]
  • 省略源:虚拟时钟,用于异步接口或I/O约束

我个人习惯:只要可能,一定指定源。省略源虽然语法上允许,但会让工具不知道这个时钟从哪里来,后续的时序分析会缺少路径信息。

举个例子:

# 在输入端口上定义时钟
create_clock -name clk_100m -period 10.0 -waveform {0 5} [get_ports clk_100m_pad]

# 在PLL输出上定义时钟
create_clock -name clk_pll -period 5.0 -waveform {0 2.5} [get_pins u_pll/clk_out]

# 虚拟时钟(没有源)
create_clock -name vclk -period 8.0

你想想看,虚拟时钟什么时候用?

当你要约束跨时钟域路径,或者约束芯片外部接口时,虚拟时钟就派上用场了。它不绑定任何物理节点,只作为一个时间参考。

小技巧: 我建议每个时钟都加上 -name 参数。虽然工具会自动生成名字,但自动生成的名字往往又长又难记。自己命名,后续的 set_clock_groups、set_false_path 用起来才顺手。

4. 多时钟定义——一个端口多个频率?

有时候,一个物理端口会输入多个频率的时钟。比如芯片有测试模式,测试时钟和功能时钟共用同一个引脚。

这时候可以定义多个时钟在同一个源上:

create_clock -name func_clk -period 10.0 -waveform {0 5} [get_ports clk_in]
create_clock -name test_clk -period 20.0 -waveform {0 10} [get_ports clk_in] -add

注意那个 -add 参数。如果不加,第二个 create_clock 会覆盖第一个。加了 -add,两个时钟共存。

嗯,这里要注意:多时钟定义时,工具会认为这些时钟是互斥的。也就是说,同一时刻只有一个时钟在活动。你需要用 set_clock_groups 来明确它们的关系。

5. 知识体系图

下面这张图,帮你理清 create_clock 的核心逻辑:

create_clock 命令知识体系 create_clock -name 时钟名 自定义,方便后续引用 -period 周期 单位ns,决定频率 -waveform 波形 {上升沿 下降沿} 源对象 端口/引脚/虚拟 [get_ports 端口名] [get_pins 引脚名] 省略(虚拟时钟) 记住:名字 + 周期 + 波形 + 源 = 完整的时钟定义 缺一个,后面就可能出问题

6. 常见错误与避坑

我总结几个最常见的错误,你对照着检查自己的脚本:

错误类型 错误写法 正确写法 后果
周期单位搞错 -period 100(想写100MHz) -period 10.0 频率变成10MHz,时序过紧
波形顺序颠倒 -waveform {5 0} -waveform {0 5} 上升沿在5ns,下降沿在0ns,工具报错
忘记 -add 第二个时钟不加 -add 加上 -add 第一个时钟被覆盖,丢失约束
源写错对象 [get_pins clk_in](端口用了get_pins) [get_ports clk_in] 工具找不到对象,报错
我曾经在一个项目中,把PLL输出时钟的周期写成了输入时钟的周期。结果PLL输出是500MHz,我写成了100MHz的周期10ns。整个芯片的时序分析全错,白白浪费了两天排查时间。从那以后,我每次写完 create_clock,都会用 report_clocks 命令检查一遍。

7. 实战建议

最后,给你几个实战中的小建议:

  • 命名规范:时钟名用 clk_频率_用途 的格式,比如 clk_100m_ddr、clk_50m_ahb。一看就懂。
  • 周期用浮点数:写 10.0 而不是 10,避免整数除法带来的精度问题。
  • 波形写完整:即使50%占空比,也写上 -waveform {0 5},让脚本自文档化。
  • 检查再检查:运行 report_clocks -verbose,确认每个时钟的周期、波形、源都正确。

时钟约束入门,说白了就是把 create_clock 这个命令用对、用熟。你把这个基础打牢了,后面的 create_generated_clock、set_clock_uncertainty 学起来就轻松多了。

嗯,今天就到这里。记住:时钟定义错了,后面全是白干。

一句话总结: create_clock 是时序约束的基石。名字要清晰,周期要准确,波形要完整,源要明确。缺一不可。

可可精品资料库