4. 时序约束:时钟定义、输入延迟、输出延迟、伪路径与多周期路径

时序约束这东西,说白了就是告诉工具你的芯片到底想怎么跑。我见过不少工程师,RTL写得飞起,一到约束就随便写两句,结果后端跑出来的时序报告根本没法看。嗯,今天咱们就把这几个最核心的约束讲透。

4.1 时钟定义:一切时序的起点

时钟是时序分析的基准。没有时钟,STA根本没法干活。我个人习惯,拿到一个新设计,第一件事就是先把所有时钟定义清楚。

# 最基本的时钟定义
create_clock -name clk_sys -period 10.000 [get_ports clk_in]

# 带波形的时钟(占空比50%,上升沿在0ns,下降沿在5ns)
create_clock -name clk_sys -period 10.000 -waveform {0 5} [get_ports clk_in]

# 虚拟时钟(用于IO约束,没有物理端口)
create_clock -name vclk -period 8.000

这里有个坑,我踩过。时钟周期单位默认是ns,但有些老工具用ps。你想想看,如果写了个-period 10,工具以为是10ps,那你的设计就得跑在100GHz——这显然不现实。

注意:时钟定义时,-waveform的默认值是{0 period/2},也就是50%占空比。如果你的时钟是DDR或者特殊占空比,一定要显式指定波形。

4.2 输入延迟:告诉工具数据什么时候来

输入延迟描述的是外部器件发送数据到芯片输入端口的时间关系。说白了,就是数据相对于时钟边沿什么时候到达。

# 最大输入延迟(setup检查用)
set_input_delay -clock vclk -max 2.500 [get_ports data_in]

# 最小输入延迟(hold检查用)
set_input_delay -clock vclk -min 0.800 [get_ports data_in]

# 带上升沿/下降沿的输入延迟
set_input_delay -clock vclk -max 2.500 -rise [get_ports data_in]
set_input_delay -clock vclk -max 2.300 -fall [get_ports data_in]

我在项目中遇到过一件事:有个同事把输入延迟设得特别大,结果工具报setup违例,他调了半天RTL。后来我一看,其实外部器件输出延迟只有1ns,他设了5ns。你想想看,这等于自己给自己挖坑。

经验之谈:输入延迟的参考时钟,建议用虚拟时钟。这样时钟树综合前后,IO约束不会因为时钟延迟变化而需要重新调整。

4.3 输出延迟:告诉工具数据什么时候走

输出延迟和输入延迟是对称的。它描述的是芯片输出数据到外部器件时,数据相对于时钟边沿的到达时间要求。

# 最大输出延迟(setup检查用)
set_output_delay -clock vclk -max 3.000 [get_ports data_out]

# 最小输出延迟(hold检查用)
set_output_delay -clock vclk -min 1.200 [get_ports data_out]

# 输出延迟也可以分rise/fall
set_output_delay -clock vclk -max 3.000 -rise [get_ports data_out]

这里有个容易搞混的地方:输出延迟是外部器件要求的建立/保持时间,不是芯片内部的延迟。我曾经见过有人把输出延迟设成负数,然后百思不得其解为什么时序总是不对。

约束类型 检查对象 典型值范围 常见错误
set_input_delay -max 芯片内部reg的setup 周期×0.2~0.4 设得太大导致过度约束
set_input_delay -min 芯片内部reg的hold 周期×0.05~0.15 设得太小导致hold违例漏报
set_output_delay -max 芯片内部reg的setup 周期×0.3~0.5 符号搞反(应为正值)
set_output_delay -min 芯片内部reg的hold 周期×0.05~0.15 忘记设-min导致hold检查不充分

4.4 伪路径:告诉工具别管这些路径

伪路径就是那些你确定不会影响芯片正常工作的路径。比如跨时钟域的同步器路径、测试模式下的路径、上电复位后的初始状态路径。

# 跨时钟域伪路径
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

# 特定路径伪路径
set_false_path -from [get_pins inst_a/reg_a/CK] -to [get_pins inst_b/reg_b/D]

# 复位路径伪路径
set_false_path -to [get_pins -hier *rst*]
核心原则:伪路径不是偷懒的借口。只有你100%确定这条路径不会影响功能,才能设成伪路径。我曾经接手过一个项目,前任工程师把一堆路径设成了伪路径,结果芯片在特定条件下功能异常——那些路径其实是有时序要求的。

嗯,这里要特别提醒:跨时钟域路径设伪路径的前提是,你已经用同步器做了正确的CDC处理。如果只是简单地把两个时钟域之间的路径设成伪路径,而没有做任何同步处理,那芯片大概率会出问题。

4.5 多周期路径:告诉工具数据可以多等几拍

多周期路径适用于那些数据不需要在一个时钟周期内完成传输的场景。比如一些计算密集型的数据路径,或者使能信号控制的路径。

# 数据需要2个时钟周期才能稳定
set_multicycle_path -setup 2 -from [get_pins inst_a/reg_a/CK] -to [get_pins inst_b/reg_b/D]

# 对应的hold检查也要调整(默认hold检查在setup前一个周期)
set_multicycle_path -hold 1 -from [get_pins inst_a/reg_a/CK] -to [get_pins inst_b/reg_b/D]

这里有个容易犯的错误:只设了-setup,忘了设-hold。默认情况下,hold检查是在setup检查的前一个时钟沿。如果你把setup改成了2个周期,但hold没改,那hold检查就会在setup检查的前一个周期——这可能导致hold违例被错误地放过。

我的习惯:设多周期路径时,-setup和-hold成对出现。一般-hold的值比-setup小1。比如setup=3,hold=2;setup=2,hold=1。

4.6 知识体系总览

下面这张图把本章的核心逻辑串起来了。你看一遍,应该能对时序约束有个整体认识。

时序约束核心知识体系 时序约束 时钟定义 输入延迟 输出延迟 伪路径 多周期路径 关键参数 周期、波形、占空比 虚拟时钟、生成时钟 关键参数 -max(setup)、-min(hold) -rise、-fall、参考时钟 关键参数 -max(setup)、-min(hold) 符号方向、参考时钟 适用场景 CDC路径、测试路径 复位路径、初始状态 关键参数 -setup N(数据N个周期后稳定) -hold N-1(hold检查前移) 核心原则:约束要准确,不要过度约束,也不要漏约束 每个约束都要有明确的物理意义和设计依据 图:时序约束五大核心要素及其关键参数

4.7 实战中的常见问题

最后,我把自己这些年遇到的一些典型问题列出来,你对照着看看,能少走不少弯路。

  1. 时钟没定义全——特别是分频时钟、门控时钟,容易漏掉。建议用report_clock检查一遍。
  2. IO延迟和实际不符——我曾经见过一个项目,输入延迟设了周期的一半,但实际外部器件输出延迟只有周期的十分之一。结果工具为了满足这个过度约束,插了大量buffer,功耗和面积都上去了。
  3. 伪路径滥用——把不确定的路径都设成伪路径,这是最危险的做法。伪路径应该是你确认不需要检查的路径,而不是你懒得检查的路径。
  4. 多周期路径忘记设hold——这个前面说过了,setup和hold要成对出现。
  5. 约束文件没有版本管理——这个虽然不是技术问题,但影响很大。约束文件改来改去,没有记录,出了问题都不知道是哪次改的。
一句话总结:时序约束不是写出来给工具看的,而是写出来给团队看的。好的约束文件,应该像一份清晰的芯片时序说明书,每个人都能看懂,每个人都能维护。