3、时钟定义:create_clock命令详解,时钟周期、占空比、波形定义

时钟,是数字芯片的“心跳”。

没有时钟,整个设计就是一盘散沙。做静态时序分析,第一步就是告诉工具:你的时钟长什么样。说白了,就是通过 create_clock 这个命令,把时钟的周期、占空比、波形这些信息喂给工具。

我刚开始学STA的时候,觉得这命令太简单了,不就是写个周期嘛。后来发现,里面坑不少。今天咱们就把它掰开揉碎了讲清楚。

3.1 最基本的时钟定义

先看一个最常用的写法:

create_clock -name clk -period 10 [get_ports clk]

这条命令干了三件事:

  • -name clk:给这个时钟起个名字,叫 clk。方便后面约束里引用。
  • -period 10:时钟周期是 10ns。也就是频率 100MHz。
  • [get_ports clk]:这个时钟是从哪个端口进来的?就是顶层叫 clk 的那个输入端口。

嗯,这里要注意:-name 不是必须的。如果你不写,工具会自动用端口名作为时钟名。但我个人习惯,每次都显式写出来,这样读约束文件的人一眼就能看懂。

小技巧: 如果你定义时钟的端口名和时钟名一样,比如端口叫 sys_clk,那 -name sys_clk 可以省略。但为了清晰,我建议别省。

3.2 时钟周期——核心中的核心

周期决定了芯片能跑多快。你想想看,周期设成 10ns,那所有逻辑路径都得在 10ns 内搞定。设成 5ns,难度直接翻倍。

周期单位是什么?纳秒(ns)。这是默认单位。你也可以用 -period 10000 表示皮秒,但一般没人这么干,容易搞混。

我在项目中遇到过一件事:有个同事把周期写成了 -period 100,他以为是 100MHz(周期10ns),结果工具按 100ns 去分析,所有路径都 slack 巨大。最后综合出来的芯片,频率根本跑不上去。这就是典型的“周期写错,全盘皆输”。

避坑指南: 我曾经因为单位搞混,把 100MHz 写成了 -period 100,导致时序分析完全失效。记住:100MHz = 10ns,不是 100ns!

3.3 占空比与波形定义

默认情况下,create_clock 定义的时钟是 50% 占空比。也就是高电平一半时间,低电平一半时间。

但现实世界没这么完美。有些时钟是 60% 占空比,有些是 40%。甚至有些 DDR 接口的时钟,占空比要求很严格。

这时候就要用到 -waveform 参数了。

# 50% 占空比,周期 10ns
create_clock -name clk -period 10 -waveform {0 5} [get_ports clk]

# 60% 占空比,高电平占 6ns
create_clock -name clk -period 10 -waveform {0 6} [get_ports clk]

# 40% 占空比,高电平占 4ns
create_clock -name clk -period 10 -waveform {0 4} [get_ports clk]

-waveform 后面跟两个值:上升沿时间下降沿时间。单位也是纳秒。

  • {0 5}:上升沿在 0ns,下降沿在 5ns。高电平 5ns,低电平 5ns。标准的 50%。
  • {0 6}:上升沿在 0ns,下降沿在 6ns。高电平 6ns,低电平 4ns。占空比 60%。

你可能会问:那下降沿在 0ns,上升沿在 5ns 行不行?

行!那就是低电平先开始,高电平在后。不过一般没人这么干,容易把自己绕晕。

重点: 如果不写 -waveform,工具默认就是 {0 period/2}。也就是 50% 占空比。所以大部分情况下,你只需要写周期就够了。

3.4 多波形时钟——同一个端口,多个频率

有些芯片有多个时钟源,通过 MUX 选择后进入同一个端口。这时候,你需要定义多个时钟,并告诉工具它们之间的关系。

create_clock -name clk_100m -period 10 -waveform {0 5} [get_ports clk_in]
create_clock -name clk_50m  -period 20 -waveform {0 10} [get_ports clk_in] -add

注意那个 -add 参数。如果不加,第二个 create_clock 会覆盖第一个。加上 -add,工具就知道这个端口上有两个时钟同时存在。

我记得有一次做项目,芯片有多个工作模式,时钟频率不同。我忘了加 -add,结果工具只分析了最后一个时钟,导致其他模式的时序完全没检查。流片回来,某个模式就是跑不起来。教训深刻啊。

警告: 同一个端口定义多个时钟时,必须-add 参数。否则后面的会覆盖前面的,导致时钟丢失。

3.5 虚拟时钟——没有物理端口的时钟

有时候,你需要定义一个时钟,但它并不对应芯片上的任何物理端口。比如,用来约束输入输出延迟的参考时钟。

这种时钟叫 虚拟时钟(virtual clock)。定义方式很简单:

create_clock -name vclk -period 10

看到了吗?没有 [get_ports ...] 这一项。这就是虚拟时钟和普通时钟的唯一区别。

虚拟时钟有什么用?我举个例子:

  • 芯片的输入数据来自外部芯片,那个芯片有自己的时钟。这个时钟不会进入我们的芯片,但我们需要用它来约束输入延迟。
  • 这时候,就定义一个虚拟时钟,代表外部芯片的时钟。

说白了,虚拟时钟就是个“假想敌”,用来帮我们算清楚外部时序的账。

个人经验: 虚拟时钟的名字最好起得有意义,比如 vclk_ext_100m,一看就知道是外部 100MHz 的虚拟时钟。别起 clk1clk2 这种,三个月后你自己都看不懂。

3.6 时钟的命名与分组

时钟多了以后,管理起来就麻烦了。我建议你养成好习惯:

  • 时钟名要有规律,比如 clk_coreclk_ioclk_ddr
  • group_path 命令把相关时钟的路径分组,方便分析。

不过这是后话了。今天先把 create_clock 吃透。

3.7 总结一下

参数 作用 是否必须 默认值
-name 时钟名称 端口名
-period 时钟周期(ns)
-waveform 上升沿和下降沿时间 {0 period/2}
-add 允许在同一个端口添加多个时钟 多时钟时必加 不加则覆盖
[get_ports ...] 时钟所在的物理端口 虚拟时钟不需要

好了,关于 create_clock 的核心内容就这些。下一章咱们聊聊 create_generated_clock,也就是分频时钟和衍生时钟的定义。那个比这个稍微复杂一点,但掌握了基础,后面就好办了。