静态时序分析(STA)原理
静态时序分析,说白了就是给芯片的每一条逻辑路径做“体检”。我刚开始接触数字设计时,总觉得STA是后端的活儿,直到自己负责的一个模块在流片后死活跑不到目标频率——翻看时序报告才发现是保持时间违规,但后端说这该前端管。那次之后我才明白,STA是前后端都要盯死的环节。
STA分析流程
STA的核心是枚举所有时序路径,然后检查每条路径上数据信号能否在时钟沿前后稳定建立和保持。流程大概分三步:
- 读入网表与约束:把综合后的门级网表和SDC时序约束喂给STA工具。
- 路径枚举与延迟计算:工具会找出所有起点(通常是时钟引脚)到终点(通常是寄存器数据端)的路径,然后根据标准单元库和互连寄生参数算出每段延迟。
- 建立/保持检查:用算出来的延迟去验证是否违反setup和hold要求。
这里有个容易忽略的点:路径必须覆盖所有四种类型——输入到寄存器、寄存器到寄存器、寄存器到输出、输入到输出。我曾经在一个项目中漏配了输入到寄存器的约束,结果STA报告全绿,流片回来IO时序全崩。
我的个人习惯:在跑STA前,先用命令 report_analysis_coverage 看看路径覆盖率是不是100%。如果低于98%,大概率漏约束了。
建立时间与保持时间检查公式
建立时间和保持时间的检查公式是STA的数学基础。很多人背公式却不懂物理含义,我建议你把它想象成“数据必须在寄存器采样窗口内稳定”。
| 检查类型 | 公式 | 物理含义 |
|---|---|---|
| 建立时间 | T_data_path + T_setup ≤ T_clock_period + T_clock_skew |
数据必须在下一个时钟沿之前到达并稳定 |
| 保持时间 | T_data_path ≥ T_hold + T_clock_uncertainty |
数据不能在当前时钟沿之后变化太快 |
你想想看,建立时间违规通常是路径太长,保持时间违规往往是路径太短。在HS‑TTA这种高吞吐架构里,流水线级数多,保持时间违规更隐蔽——因为大量路径的延迟都很接近,稍微一点工艺偏差就可能违例。
时序报告解读
拿到STA报告后,我一般先看 slack 列。slack是负值的地方就是违规点。但光看这一个数字不够,得逐层拆报告。
典型的report_timing输出长这样:
Startpoint: u_ff_a_reg/CK
Endpoint: u_ff_b_reg/D
Path Type: setup
Slack: -0.12ns
Data Path:
u_ff_a_reg/Q 0.00ns
U1/A 0.15ns
U2/Z 0.28ns
u_ff_b_reg/D 0.43ns
Clock Path:
clk (rise edge) 0.00ns
u_ff_a_reg/CK 0.05ns
u_ff_b_reg/CK 0.08ns
关键字段解读:
- Slack:负值表示时序余量不足,值就是欠了多少时间。
- Data Path:从起点Q端到终点D端的实际延迟。
- Clock Path:时钟从源点到起点和终点的延迟差(skew在这里体现)。
如果看到建立时间违例,而我确定代码逻辑没问题,那我会先检查时钟是否太慢——哦不对,是时钟周期是否足够。如果是保持时间违例,我曾经被一个假路径骗过:报告里显示一条路径slack为‑0.01ns,我以为是边缘违例,加了两个Buffer才跑通。后来发现那是异步时钟域间的路径,应该在约束里设false_path。所以看报告前,一定要确认路径的约束类型。
我曾经踩过的坑:有一次报告里所有setup违例都集中在同一个模块的输出端口。我盯着数据路径看了半天,最后发现是输出端口没有设output_delay。所以读报告时先看 Path Type,再看 Startpoint/Endpoint 是不是和自己预期的输入输出端口匹配。
STA在HS‑TTA中的特殊考量
HS‑TTA架构里经常有多周期路径和流水线握手信号。这些东西在STA里需要额外标注。比如一个数据需要两个周期才能到达终点,你就得在SDC里写set_multicycle_path -setup 2。不然工具会按单周期检查,报一堆假违例。
另外,HS‑TTA的时钟分布对jitter要求很高。我建议你在约束里加入合理的clock_uncertainty值,别用默认的0。真实项目中我曾因为uncertainty设得过小,导致上板测试时偶尔时序跑飞。
下面这张图总结了STA的核心路径分类:
对应到我们HS‑TTA项目里,寄存器到寄存器的路径往往占85%以上。我在调试setup违例时,会优先看这条路径上有没有插不必要的Buffer,或者能不能通过retiming把逻辑移到相邻级。
STA报告解读是门手艺活。我的经验是:先看总违例数,再看最差违例路径的slack,接着看路径类型。如果全是同一个类型的违例,比如全是寄存器到输出,那大概率是输出约束配错了。
注意:不要一上来就调代码或插Buffer。先确认约束本身是否正确——我曾经花了两天优化一条路径,最后发现是时钟周期设错了。正确的调试顺序永远是:确认约束 → 验证路径 → 分析延迟 → 修改设计。