时钟约束入门:create_clock命令详解

时钟,说白了就是芯片的「心跳」。没有它,整个设计就是一盘散沙。我刚开始做时序约束时,觉得时钟不就是个方波嘛,有什么好约束的?直到有一次,因为时钟定义错了,整个芯片的时序分析全乱套了……嗯,从那以后,我对时钟约束再也不敢马虎了。

时钟周期与占空比:最基础的两个参数

先说说时钟周期。它指的是时钟信号重复一次的时间长度,单位通常是纳秒(ns)。比如一个100MHz的时钟,周期就是10ns。这个很好理解,对吧?

占空比呢,就是高电平时间占整个周期的比例。标准情况下是50%,也就是一半时间高、一半时间低。但实际项目中,有些IP核会要求非50%的占空比,比如DDR接口就经常用到。

核心公式:

  • 时钟频率(MHz)= 1000 / 周期(ns)
  • 高电平时间 = 周期 × 占空比
  • 低电平时间 = 周期 × (1 - 占空比)

我个人习惯,在写约束之前,先确认好时钟频率和占空比。这两个参数错了,后面全白搭。

create_clock命令详解

这个命令是时序约束的「敲门砖」。它的基本语法长这样:

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

我来拆开讲讲每个参数:

参数 说明 示例
-name 给时钟起个名字,方便后面引用 -name clk_100m
-period 时钟周期,单位ns -period 10
-waveform 定义上升沿和下降沿的时间点 -waveform {0 5}
-add 在同一个端口上添加多个时钟 -add

举个实际例子。假设我们有个100MHz的时钟,占空比50%,从端口clk_in进来:

create_clock -name sys_clk -period 10 -waveform {0 5} [get_ports clk_in]

这里-waveform {0 5}的意思是:上升沿在0ns时刻,下降沿在5ns时刻。说白了就是高电平从0到5ns,低电平从5到10ns。正好50%占空比。

小技巧:如果不写-waveform,工具默认就是50%占空比,上升沿在0时刻,下降沿在周期的一半。所以上面的例子可以简写为:

create_clock -name sys_clk -period 10 [get_ports clk_in]

非50%占空比的时钟

我曾经遇到过一个项目,DDR控制器要求时钟占空比是45%。当时我直接写了:

create_clock -name ddr_clk -period 5 -waveform {0 2.25} [get_ports ddr_ck]

2.25ns就是5ns周期的45%。嗯,这里要注意,waveform里的时间单位是ns,可以写小数。

时钟源与时钟组

一个复杂芯片里,时钟不会只有一个。可能有几十个甚至上百个时钟域。这时候就需要把时钟分分组。

时钟源,指的是时钟从哪里来。可能是:

  • 芯片外部引脚进来的(primary clock)
  • PLL生成的(generated clock)
  • 内部逻辑分频得到的

时钟组呢,就是把相关的时钟放在一起。比如所有100MHz的时钟可以归为一组,所有DDR相关的时钟归为另一组。这样做的好处是,在做时序分析时,可以只分析同一组内的时钟路径,不同组之间的路径可以忽略或单独处理。

避坑指南:我曾经犯过一个错误——把两个不同频率的时钟定义在同一个端口上,但没有加-add参数。结果后面的定义把前面的覆盖了,导致整个时序分析结果全错。所以,如果你要在同一个端口上定义多个时钟,一定要加上-add!

多个时钟在同一个端口

有些场景下,同一个物理端口会输入多个时钟。比如芯片有测试模式,测试时钟和功能时钟共用引脚。这时候可以这样写:

create_clock -name func_clk -period 10 [get_ports clk_in]
create_clock -name test_clk -period 100 -add [get_ports clk_in]

加上-add之后,两个时钟就都定义在clk_in上了。工具在做分析时,会分别考虑两种模式下的时序。

时钟组的使用

假设我们有三个时钟域:CPU时钟(500MHz)、总线时钟(200MHz)、外设时钟(50MHz)。我们可以这样分组:

create_clock -name cpu_clk -period 2 [get_ports cpu_ck]
create_clock -name bus_clk -period 5 [get_ports bus_ck]
create_clock -name per_clk -period 20 [get_ports per_ck]

set_clock_groups -asynchronous -group {cpu_clk} -group {bus_clk} -group {per_clk}

这里我用了-asynchronous参数,意思是这三个时钟域之间是异步的,不需要做跨时钟域的时序分析。你想想看,如果不对它们分组,工具会去分析所有跨时钟域的路径,那分析时间会变得非常长,而且会产生大量假路径(false path)。

我的经验:时钟分组是时序约束里最容易忽略但又最重要的环节之一。我见过有人把所有时钟都放在一个组里,结果STA跑了整整一天还没跑完。合理分组后,半小时就搞定了。

知识体系结构图

下面这张图,是我自己总结的时钟约束入门知识体系。你看一眼就能明白各个知识点之间的关系:

时钟约束入门知识体系 create_clock 时钟周期(-period) 频率 = 1000 / 周期 单位:纳秒(ns) 常见频率:100M/200M/500M 占空比(-waveform) 默认50% DDR常用45%/55% 时钟源 外部引脚(primary) PLL生成(generated) 内部分频 时钟组(set_clock_groups) -asynchronous(异步组) -group {时钟列表} 减少STA分析时间 核心:周期 + 占空比 + 时钟源 + 时钟组

这张图把时钟约束的四个核心要素串起来了。你从中间开始看,往四个方向发散,每个分支都有对应的参数和注意事项。我个人觉得,把这四个点搞清楚了,时钟约束就算入门了。

最后说一句:时钟约束是STA的基础,也是芯片能否正常工作的关键。我建议你在写约束时,先画个时钟树草图,把每个时钟的源头、频率、占空比都标清楚。然后再动手写create_clock命令。这样不容易出错。

可可精品资料库