静态时序分析(STA)入门:时序路径的概念,建立时间与保持时间,时序约束文件(SDC)的编写

静态时序分析,说白了就是检查你的芯片能不能跑在目标频率上。我刚开始做设计时总觉得这步可有可无,直到有一次流片回来芯片死活上不了高频……嗯,从那以后我再也不敢跳过 STA 了。

今天咱们聊聊 STA 的三个核心:时序路径、建立时间与保持时间、还有 SDC 约束怎么写。你面试时被问到的概率,几乎百分之百。

一、时序路径:芯片里的“高速公路”

时序路径是什么?其实就是信号从起点跑到终点的路线。我习惯把它分成四类:

  • 输入到寄存器:从芯片引脚到内部触发器
  • 寄存器到寄存器:从一个触发器到另一个触发器
  • 寄存器到输出:从内部触发器到芯片引脚
  • 输入到输出:纯组合逻辑路径

你想想看,面试官问“STA 主要分析哪些路径”,你把这四条甩出来,基本就稳了。其中寄存器到寄存器是最常见的,也是我们写约束时最关心的。

核心要点:每条路径都有起点(startpoint)和终点(endpoint)。起点通常是时钟引脚或输入端口,终点是数据引脚或输出端口。路径上的延迟决定了你的芯片能跑多快。

二、建立时间与保持时间:一对“冤家”

这两个概念,我当年学的时候绕了好久。其实没那么玄乎。

建立时间(setup time):数据在时钟有效沿到来之前,必须保持稳定的最短时间。说白了,就是数据得提前“坐好”,别等时钟来了还在路上跑。

保持时间(hold time):数据在时钟有效沿之后,必须保持稳定的最短时间。也就是时钟来了之后,数据不能马上溜走。

我在项目中遇到过最坑的事:一个同事把建立时间余量算得死死的,结果保持时间违例了。改了半天,发现是时钟树没做好,数据跑得太快。嗯,这里要注意——建立时间和保持时间是一对矛盾。你优化了 setup,可能就恶化了 hold。

个人经验:检查时序时,我习惯先看 setup,再看 hold。因为 setup 违例通常意味着频率上不去,而 hold 违例往往可以通过插入延迟 buffer 来修。但千万别反过来做,否则你会很痛苦。

三、时序约束文件(SDC)的编写

SDC 是 STA 的“指挥棒”。没有约束,工具就不知道你要分析什么。我见过太多人随便写个时钟就跑了,结果后仿一堆问题。

下面是我常用的几条核心命令:

# 创建时钟
create_clock -name clk -period 10 [get_ports clk]

# 生成时钟(用于分频时钟)
create_generated_clock -name clk_div -source [get_ports clk] -divide_by 2 [get_pins u_div/clk_out]

# 输入延迟约束
set_input_delay -clock clk -max 5 [get_ports data_in]
set_input_delay -clock clk -min 2 [get_ports data_in]

# 输出延迟约束
set_output_delay -clock clk -max 6 [get_ports data_out]
set_output_delay -clock clk -min 3 [get_ports data_out]

# 伪路径(不关心的路径)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

# 多周期路径
set_multicycle_path -setup 2 -from [get_pins u_reg1/CK] -to [get_pins u_reg2/D]

这里我重点说几个坑:

  • 时钟定义要准确:period 单位是 ns,别写成 ps。我曾经见过有人把 100MHz 写成 period 100,结果工具以为频率是 10MHz。
  • 输入输出延迟要合理:set_input_delay 和 set_output_delay 的值,通常来自芯片外部器件的时序参数。别瞎填,否则 STA 结果毫无意义。
  • 伪路径别乱用:只有你确定信号不会影响功能时,才用 set_false_path。否则你可能会漏掉真正的时序问题。

避坑指南:我曾经在一个项目中,把异步 FIFO 的跨时钟域路径设成了 false_path,结果后仿时数据采样出错。后来才意识到,false_path 只是不检查时序,但信号还是要保证稳定的。正确的做法是用 set_clock_groups 或同步器来处理。

四、实战:一个完整的 SDC 示例

假设我们有一个模块,输入时钟 100MHz,数据从外部芯片来,延迟范围 2~5ns,输出到外部芯片,要求延迟 3~6ns。内部还有一个分频时钟 50MHz。

# 时钟定义
create_clock -name clk_100m -period 10 [get_ports clk]
create_generated_clock -name clk_50m -source [get_ports clk] -divide_by 2 [get_pins u_div/clk_out]

# 输入约束
set_input_delay -clock clk_100m -max 5 [get_ports data_in]
set_input_delay -clock clk_100m -min 2 [get_ports data_in]

# 输出约束
set_output_delay -clock clk_100m -max 6 [get_ports data_out]
set_output_delay -clock clk_100m -min 3 [get_ports data_out]

# 跨时钟域处理(异步)
set_clock_groups -asynchronous -group [get_clocks clk_100m] -group [get_clocks clk_50m]

# 多周期路径(假设某个寄存器每两个周期才采样一次)
set_multicycle_path -setup 2 -from [get_pins u_reg1/CK] -to [get_pins u_reg2/D]
set_multicycle_path -hold 1 -from [get_pins u_reg1/CK] -to [get_pins u_reg2/D]

你想想看,这个 SDC 写完后,工具就能自动分析所有路径的 setup 和 hold 了。如果报告里有违例,你就得回去改设计或者调整约束。

我的习惯:写完 SDC 后,先跑一遍 check_timing,看看有没有未约束的路径。这一步很多人会忽略,但恰恰是发现问题的好机会。我记得有一次 check_timing 报出几十条未约束路径,原来是忘了给某个时钟端口加约束。

五、总结

静态时序分析没那么可怕。你只要记住三件事:

  1. 时序路径:搞清楚信号从哪里来,到哪里去
  2. 建立与保持:setup 看频率,hold 看数据稳定性
  3. SDC 约束:准确、完整、合理

面试时,如果能结合项目经验讲出这些细节,面试官一定会觉得你是个有实战经验的人。嗯,今天就聊到这里,下次咱们讲讲时钟树综合和时序收敛的技巧。