1. 时序路径分析到底在解决什么问题
1.1 从一个真实翻车案例说起
前两年我接手过一颗中等规模的SoC,前端功能验证全绿,综合面积和功耗都在预算内,团队都觉得稳了。结果后端做完布局布线,PrimeTime一跑,建立时间违例最大的一条路径差了1.2ns,频率直接从目标500MHz掉到380MHz才能收敛。回头一查,问题出在一个跨时钟域的信号上:设计者用了一个两级同步器,但同步器第一级的输出扇出到了三个不同的时钟域,SDC里只对其中一个域做了约束,另外两个域在时序分析里被当成"假路径"忽略了。这个坑不复杂,但代价是两周的返工。
这件事让我意识到,时序路径分析不是后端工程师的专属技能,前端设计者如果不懂路径是怎么被划分、被约束、被检查的,就等于把定时炸弹埋进了RTL里。而PrimeTime作为业界事实标准的STA(Static Timing Analysis,静态时序分析)工具,配合SDC(Synopsys Design Constraints)约束文件,就是拆解这颗炸弹的排爆工具。
这篇文章面向的是刚接触时序分析、或者做了几年RTL但没系统学过STA的工程师。我会把时序路径的分类逻辑、PrimeTime的分析机制、SDC约束的写法,以及实际项目中踩过的坑,掰开揉碎讲清楚。看完你应该能独立写出一份可用的SDC,并且知道PrimeTime报出来的每一条路径为什么会被检查、为什么会被忽略。
1.2 静态时序分析的本质:穷举而非仿真
很多人第一次接触STA会有一个疑问:不跑激励、不做仿真,怎么知道电路能不能工作?
关键在于,STA不关心电路"做什么",只关心信号"能不能按时到"。它把设计拆成一条条从起点到终点的路径,计算每条路径的延迟,然后和时钟周期比较。只要所有路径在最坏情况下都能满足时间要求,电路在功能正确的前提下就能跑对频率。这是一种穷举式的、与输入向量无关的验证方法。
对比一下动态仿真:你跑一万个测试向量,可能覆盖了99%的路径,但剩下1%的路径恰好是时序最紧的那条,仿真跑不到就发现不了。STA不做概率游戏,它把所有路径都算一遍,一条不漏。代价是它需要准确的约束——约束错了,STA算得再快也是错的。
这就是为什么SDC如此重要。SDC告诉PrimeTime:时钟周期是多少、哪些路径需要检查、哪些路径可以忽略、输入输出延迟是多少。约束写错,等于给排爆工具设了错误的参数。
1.3 时序路径的四种基本类型
在深入PrimeTime之前,必须先把路径分类搞清楚。所有时序路径,无论多复杂,都可以归入以下四类:
| 路径类型 | 起点 | 终点 | 检查类型 | 典型场景 |
|---|---|---|---|---|
| 输入到寄存器 | 输入端口 | 寄存器D端 | setup/hold | 外部信号进入芯片 |
| 寄存器到寄存器 | 寄存器CLK端 | 寄存器D端 | setup/hold | 芯片内部逻辑 |
| 寄存器到输出 | 寄存器CLK端 | 输出端口 | setup/hold | 芯片输出到外部 |
| 输入到输出 | 输入端口 | 输出端口 | 通常不检查 | 纯组合逻辑穿透 |
这四类路径的检查方式不同,约束方式也不同。输入到寄存器的路径,起点是输入端口,你需要用set_input_delay告诉工具外部信号相对于时钟的到达时间;寄存器到寄存器的路径,起点是时钟引脚,工具会自动从时钟定义推导;寄存器到输出的路径,终点是输出端口,你需要用set_output_delay描述外部接收端的时序要求。
输入到输出的纯组合路径,默认情况下PrimeTime不做时序检查,因为这种路径的延迟取决于外部环境,芯片内部无法独立约束。如果你确实需要检查,得手动用set_max_delay或set_min_delay指定。
注意:很多初学者会把"输入到输出"路径当成不需要管的路径,但在某些接口协议(比如某些异步握手协议)里,这条路径的延迟是有上限的,必须显式约束。
2. PrimeTime的分析机制与SDC约束逻辑
2.1 PrimeTime是怎么"看"电路的
PrimeTime读入两个核心输入:网表(通常是综合后的.v文件或.db库文件)和SDC约束。它内部会把网表转换成一个时序图(timing graph),图中的节点是引脚,边是时序弧(timing arc)。每条弧有一个延迟值,来自工艺库(.lib)中的查找表。
然后PrimeTime在这个图上做图论意义上的最长路径和最短路径搜索。最长路径用于setup检查,最短路径用于hold检查。搜索的起点和终点由SDC中的时钟定义和路径约束决定。
这里有个关键概念叫时序弧。一个标准单元内部,从输入引脚到输出引脚有延迟弧;一个触发器的时钟引脚到输出Q端有延迟弧;建立时间和保持时间本身也是弧,不过是"检查弧"(check arc),不传递信号,只做比较。
PrimeTime的路径搜索是基于时钟域的。它先根据SDC中的create_clock和create_generated_clock建立时钟树,然后对每个时钟域内的路径做分析。跨时钟域的路径,如果没有特殊约束,默认会被当作异步路径忽略——这就是我开头那个案例翻车的根本原因。
2.2 SDC约束的层次结构
一份完整的SDC,逻辑上分四层:
第一层:时钟定义。这是所有约束的基础。create_clock定义主时钟,create_generated_clock定义分频、倍频或门控时钟。时钟的周期、占空比、边沿时间都在这里指定。
第二层:时钟特性。set_clock_uncertainty设置时钟抖动和偏差余量,set_clock_latency设置时钟树延迟,set_clock_transition设置时钟边沿转换时间。这些参数直接影响setup和hold的余量计算。
第三层:I/O约束。set_input_delay和set_output_delay描述芯片边界之外的时序环境。这两个约束必须配合create_clock使用,否则工具不知道延迟是相对于哪个时钟沿的。
第四层:时序例外。set_false_path、set_multicycle_path、set_max_delay、set_min_delay。这些是"例外条款",告诉工具哪些路径不需要按默认规则检查,或者按特殊规则检查。
这四层的顺序不能乱。时钟没定义,I/O约束就没有参考系;I/O约束没写,边界路径的分析就是空中楼阁;时序例外没设对,要么漏检真实违例,要么把大量无辜路径报成违例,淹没真正的问题。
2.3 建立时间与保持时间的计算模型
理解PrimeTime报出的slack,必须理解setup和hold的计算公式。
Setup检查:数据到达时间必须早于时钟捕获沿减去建立时间。
setup_slack = (捕获时钟沿到达时间 - setup_time) - 数据到达时间其中数据到达时间 = 发射时钟沿到达时间 + 时钟到输出延迟(Tco)+ 组合逻辑延迟。捕获时钟沿到达时间 = 时钟周期 + 时钟延迟 - 时钟不确定性。
Hold检查:数据在捕获沿之后必须保持足够长时间。
hold_slack = 数据到达时间 - (捕获时钟沿到达时间 + hold_time)注意hold检查里,数据到达时间用的是最快路径(最短延迟),捕获时钟沿用的是同一个时钟沿(不是下一个周期)。这是hold和setup最本质的区别:setup看的是"数据能不能在下一个沿之前到",hold看的是"数据会不会在同一个沿之后跑得太快"。
实操心得:很多新手看到hold违例就想去加延迟,但hold违例的根因往往是时钟树偏差太大,或者数据路径太短。先查时钟树,再查数据路径,顺序不能反。
2.4 时钟不确定性对slack的影响
set_clock_uncertainty是SDC里最容易被低估的参数。它同时影响setup和hold:
- 对setup:uncertainty越大,捕获沿越"提前",slack越小,越悲观。
- 对hold:uncertainty越大,捕获沿越"延后",slack越小,同样越悲观。
在综合阶段,时钟树还没建,uncertainty要设得大一些(通常0.15~0.3ns),给后端留余量。到了布局布线后,时钟树建好了,uncertainty可以收紧到0.05~0.1ns。如果前后端用同一个uncertainty值,要么综合过度悲观导致面积浪费,要么后端余量不足导致收敛困难。
我一般会在SDC里用变量区分阶段:
# 综合阶段 set_clock_uncertainty 0.2 [get_clocks clk_core] # 布局布线后 # set_clock_uncertainty 0.08 [get_clocks clk_core]切换阶段时改一行注释就行,比到处改数字靠谱。
3. 从零写一份可用的SDC:实操全流程
3.1 时钟定义:一切约束的起点
假设我们有一颗芯片,外部晶振输入24MHz,内部PLL倍频到480MHz作为核心时钟,另有一个32.768kHz的低频时钟用于RTC。
# 主时钟:外部晶振输入 create_clock -name clk_ref -period 41.667 [get_ports clk_ref_pad] # 24MHz对应41.667ns # PLL输出时钟 create_clock -name clk_core -period 2.083 [get_ports clk_core_test] # 480MHz对应2.083ns # RTC低频时钟 create_clock -name clk_rtc -period 30517.6 [get_ports clk_rtc_pad] # 32.768kHz对应30517.6ns这里有个细节:PLL输出时钟在网表里通常不是直接从端口出来的,而是PLL模块的输出引脚。如果PLL是硬核,它的输出引脚在网表里存在,可以直接create_clock;如果PLL是软核,输出时钟是内部生成的,应该用create_generated_clock。
# 如果PLL是软核,用generated clock create_generated_clock -name clk_core \ -source [get_ports clk_ref_pad] \ -divide_by 1 -multiply_by 20 \ [get_pins pll_inst/CLK_OUT]-divide_by和-multiply_by的组合决定了倍频关系。24MHz乘以20等于480MHz,周期2.083ns。这个周期值必须和create_clock里写的一致,否则后续所有时序计算都会偏。
注意:
create_generated_clock的-source必须指向一个已经定义的时钟源。如果source写错,工具会报"no clock found",但不会告诉你具体哪里错了,排查起来很费时间。建议每定义完一个时钟就用report_clocks确认一下。
3.2 时钟特性:uncertainty、latency、transition
时钟定义完之后,紧接着设置时钟特性。这三个参数直接决定slack的悲观程度。
# 综合阶段:uncertainty设大一些 set_clock_uncertainty -setup 0.25 [get_clocks clk_core] set_clock_uncertainty -hold 0.15 [get_clocks clk_core] # 时钟树延迟:综合阶段用估计值 set_clock_latency -source 0.5 [get_clocks clk_core] set_clock_latency 0.3 [get_clocks clk_core] # 时钟转换时间 set_clock_transition 0.1 [get_clocks clk_core]set_clock_latency分两种:带-source的是时钟源到芯片时钟根节点的延迟,不带的是时钟根节点到寄存器时钟引脚的延迟。综合阶段两者都要估,布局布线后PrimeTime会从时钟树报告里读实际值,这两个约束就可以去掉了。
set_clock_transition在综合阶段也很重要,因为综合工具需要知道时钟边沿有多"陡"。设得太乐观(比如0.01ns),综合出来的时钟树可能驱动能力不足;设得太悲观(比如0.3ns),综合会过度插入缓冲器,面积和功耗都上去了。一般0.1~0.15ns是比较合理的估计。
3.3 I/O约束:边界路径的时序描述
I/O约束是SDC里最容易写错的部分,因为它需要你理解芯片外部的时序环境。
假设clk_core的480MHz时钟也输出到芯片外部,外部有一个同步SRAM,数据在时钟上升沿后2ns到达芯片输入端口,芯片输出数据需要在时钟上升沿前1.5ns稳定。
# 输入延迟:外部数据相对于时钟沿的到达时间 set_input_delay -clock clk_core -max 2.0 [get_ports data_in*] set_input_delay -clock clk_core -min 0.5 [get_ports data_in*] # 输出延迟:外部接收端要求的建立和保持时间 set_output_delay -clock clk_core -max 1.5 [get_ports data_out*] set_output_delay -clock clk_core -min 0.3 [get_ports data_out*]-max用于setup检查,-min用于hold检查。为什么输入延迟的min不是0?因为外部器件的输出也有保持时间要求,数据不会在时钟沿瞬间就变化,总有一个最小到达时间。这个值通常来自外部器件的datasheet。
如果输入输出是相对于时钟的下降沿,需要加-clock_fall:
set_input_delay -clock clk_core -clock_fall -max 2.0 [get_ports data_in*]实操心得:I/O约束写完一定要用
report_timing -from [get_ports data_in*]检查一下,看看工具是否真的把这些端口当成了时序起点。如果报告里显示"no path",多半是时钟没关联上,或者端口名写错了。
3.4 时序例外:false path与multicycle path
时序例外是SDC里最危险的部分。写对了,工具忽略该忽略的路径;写错了,真实违例被掩盖,芯片流片回来直接报废。
False path用于声明某条路径不需要做时序检查。典型场景:
- 跨时钟域且已经做了同步处理的路径
- 配置寄存器到功能逻辑的路径(配置在芯片启动时完成,之后不变)
- 测试逻辑到功能逻辑的路径
# 跨时钟域同步器路径 set_false_path -from [get_clocks clk_core] -to [get_clocks clk_rtc] set_false_path -from [get_clocks clk_rtc] -to [get_clocks clk_core] # 配置寄存器路径 set_false_path -from [get_cells cfg_reg*] -to [get_cells -hier -filter "ref_name=~*_reg*"]第二条约束要特别小心。get_cells -hier -filter会匹配所有层次下的寄存器,如果配置寄存器的输出扇出到了功能逻辑,这条false path会把真实路径也忽略掉。更安全的做法是指定具体的终点:
set_false_path -from [get_cells cfg_reg*] -to [get_pins sync_inst/D]Multicycle path用于声明某条路径需要多个时钟周期才能完成。典型场景:
- 乘法器、除法器等复杂运算单元
- 慢速外设接口
- 分频时钟之间的数据传递
# 乘法器需要2个周期完成 set_multicycle_path 2 -setup -from [get_cells mult_inst/*] -to [get_cells result_reg*] set_multicycle_path 1 -hold -from [get_cells mult_inst/*] -to [get_cells result_reg*]注意setup和hold的multicycle值要配合。setup设2,hold通常设1(即setup值减1)。如果hold也设2,工具会把hold检查也推迟一个周期,导致hold过于宽松,可能漏掉真实的hold违例。
注意:multicycle path的hold约束经常被遗忘。如果只写setup不写hold,PrimeTime会默认hold也在同一个周期检查,导致hold过于悲观,可能报出大量假违例。记住口诀:setup设N,hold设N-1。
3.5 约束的验证与调试
SDC写完不是终点,验证才是。我通常按以下顺序检查:
report_clocks:确认所有时钟都定义了,周期正确。report_clock -skew:检查时钟之间的相位关系。check_timing:PrimeTime自带的约束检查命令,会报告未约束的端点、未定义的时钟、冲突的约束等。report_timing -max_paths 10:看最差的10条路径,确认它们是你预期的路径。report_timing -from [get_ports *]:检查I/O路径是否被正确约束。
check_timing是最重要的。它会列出所有"unconstrained endpoint",也就是没有被任何时钟或约束覆盖的寄存器D端。这些端点在时序分析里是盲区,必须逐个确认是故意忽略还是漏约束。
# 检查未约束端点 check_timing -verbose如果报告里出现大量unconstrained endpoint,先别急着加约束,先查时钟定义是否覆盖了所有时钟域。很多时候是某个分频时钟忘了定义,导致整个时钟域都是盲区。
4. 常见问题与排查技巧实录
4.1 PrimeTime报"no path"的几种原因
这是新手最常遇到的问题:明明写了约束,report_timing却报"no path"。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 所有路径都no path | 时钟未定义或网表未读入 | report_clocks确认时钟存在 |
| 特定端口no path | 端口名拼写错误 | get_ports确认端口存在 |
| 跨时钟域no path | 默认异步忽略 | 检查是否需要false path或max_delay |
| 输入端口no path | 缺少input_delay | report_timing -from [get_ports ...] |
| 输出端口no path | 缺少output_delay | report_timing -to [get_ports ...] |
我遇到最多的是端口名拼写错误。Verilog里端口名是data_in[7:0],SDC里写成data_in*,如果实际端口名是data_i[7:0],通配符就匹配不上。用get_ports *列出所有端口,复制粘贴比手写靠谱。
4.2 时钟树偏差导致的hold违例
Hold违例在后端阶段很常见,根因通常是时钟树偏差(clock skew)。如果捕获寄存器的时钟比发射寄存器的时钟早到,数据跑得太快,hold就违例。
排查步骤:
report_clock_timing -type skew查看时钟树偏差。- 如果skew超过周期的10%,说明时钟树需要优化。
- 检查时钟树综合(CTS)的约束,看是否指定了合理的skew目标。
- 如果skew无法进一步优化,考虑在数据路径上插入延迟单元。
实操心得:hold违例不要一上来就加延迟。先看skew,再看数据路径。很多时候是时钟树某个分支的缓冲器太少,导致该分支时钟到得早。在CTS阶段调整缓冲器数量,比在数据路径上加延迟更干净。
4.3 Multicycle path写错导致的假违例
前面提到setup设N、hold设N-1。如果只写setup不写hold,PrimeTime会按默认的hold检查,也就是在同一个周期检查。对于一条实际需要2个周期的路径,数据在第一个周期还没稳定,hold检查自然违例。
# 错误写法:只写setup set_multicycle_path 2 -setup -from [get_cells mult_inst/*] -to [get_cells result_reg*] # 正确写法:setup和hold配合 set_multicycle_path 2 -setup -from [get_cells mult_inst/*] -to [get_cells result_reg*] set_multicycle_path 1 -hold -from [get_cells mult_inst/*] -to [get_cells result_reg*]还有一种常见错误:multicycle的起点和终点写反了。-from是发射寄存器,-to是捕获寄存器。如果写反,约束作用在错误的路径上,真实路径反而没被约束。
4.4 False path过度约束的隐患
False path是"核武器",用好了能大幅减少分析时间,用错了会掩盖真实违例。我见过最危险的一种写法:
set_false_path -from [all_inputs] -to [all_registers]这条约束把所有输入到寄存器的路径都忽略了。如果芯片有真实的输入数据路径,这些路径的setup违例就永远不会被报出来。流片回来发现功能不对,回头查时序,才发现约束把真实路径屏蔽了。
安全的做法是精确指定起点和终点:
set_false_path -from [get_ports test_mode] -to [get_cells -hier -filter "ref_name=~*_reg*"]只忽略test_mode端口到寄存器的路径,其他输入端口不受影响。
4.5 约束冲突的排查
当两条约束作用于同一条路径时,PrimeTime的行为取决于约束的优先级。一般来说,更具体的约束优先于更宽泛的约束,false path优先于其他所有约束。
如果发现某条路径的slack和预期不符,用report_timing -path_type full_clock_expanded查看完整的时钟路径,确认工具用的是哪个时钟沿、哪个uncertainty值。然后用report_exceptions查看所有生效的时序例外,确认是否有冲突的false path或multicycle path。
# 查看所有时序例外 report_exceptions # 查看特定路径的详细时序报告 report_timing -from [get_pins reg_a/Q] -to [get_pins reg_b/D] -path_type full_clock_expandedreport_exceptions会列出所有false path、multicycle path、max_delay、min_delay,以及它们是否被应用。如果某条例外显示"not applied",说明它的起点或终点没有匹配到任何路径,需要检查get_cells或get_pins的写法。
4.6 综合与布局布线阶段的约束差异
同一个设计,综合阶段和布局布线阶段的SDC不能完全一样。主要差异在时钟特性:
| 参数 | 综合阶段 | 布局布线后 |
|---|---|---|
| clock_uncertainty | 0.15~0.3ns | 0.05~0.1ns |
| clock_latency | 估计值 | 从时钟树报告读取 |
| clock_transition | 估计值 | 从时钟树报告读取 |
| input_delay | 保守估计 | 根据实际接口调整 |
| output_delay | 保守估计 | 根据实际接口调整 |
综合阶段用保守值,给后端留余量;后端用实际值,避免过度悲观。如果后端还用综合阶段的uncertainty,会导致大量假违例,浪费优化时间。
我通常把SDC拆成两个文件:constraints_common.sdc放时钟定义、I/O约束、时序例外,两个阶段共用;constraints_synth.sdc和constraints_pnr.sdc放时钟特性,各自不同。用include命令组合:
# constraints_synth.sdc include constraints_common.sdc set_clock_uncertainty 0.25 [get_clocks clk_core] set_clock_latency -source 0.5 [get_clocks clk_core]这样切换阶段时只需要换一个文件,公共约束不会漏改。
4.7 常见问题速查表
| 问题 | 现象 | 解决方法 |
|---|---|---|
| 时钟未定义 | 所有路径no path | create_clock补定义 |
| 端口名错误 | 特定端口no path | get_ports *确认名称 |
| 缺少I/O约束 | 边界路径no path | 补set_input_delay/set_output_delay |
| false path过度 | 真实违例被掩盖 | 精确指定起点终点 |
| multicycle缺hold | 假hold违例 | 补set_multicycle_path N-1 -hold |
| uncertainty过大 | 大量假setup违例 | 后端阶段收紧uncertainty |
| 时钟树偏差大 | hold违例集中 | 优化CTS,调整缓冲器 |
| 约束冲突 | slack与预期不符 | report_exceptions排查 |
5. 从约束到收敛:时序分析的工程化思路
5.1 时序收敛的迭代节奏
时序收敛不是一次性的工作,而是一个迭代过程。典型的节奏是:
- 综合后跑STA,看WNS(最差负slack)和TNS(总负slack)。
- 如果WNS在-0.5ns以内,直接进布局布线,后端优化能收敛。
- 如果WNS超过-1ns,先回RTL改逻辑,或者调整综合策略。
- 布局布线后跑STA,看实际时钟树下的slack。
- 如果还有违例,用PrimeTime的ECO(工程变更命令)功能做局部优化。
- 迭代直到WNS和TNS都满足目标。
每一步都要保存时序报告,对比前后变化。如果某次优化后TNS反而变大了,说明优化方向错了,需要回退。
5.2 时序报告的关键指标
PrimeTime的时序报告里,除了slack,还有几个指标值得关注:
- WNS:最差负slack,决定芯片能跑的最高频率。
- TNS:所有负slack的总和,反映违例的严重程度。
- NVP(Number of Violating Paths):违例路径数量,反映违例的广度。
- WHS:最差hold slack,决定芯片的最低工作频率。
WNS和TNS要一起看。WNS很小但TNS很大,说明有一条特别差的路径拖后腿,优化这条路径就能大幅改善TNS。WNS和TNS都很大,说明时序问题很普遍,需要从架构层面解决。
5.3 跨时钟域路径的处理策略
跨时钟域路径是时序分析里最棘手的问题。处理策略取决于同步方式:
- 两级同步器:第一级到第二级的路径通常设false path,因为同步器的目的是防止亚稳态传播,不是传递精确数据。
- 异步FIFO:读写指针的比较逻辑需要特殊约束,通常用
set_max_delay限制指针比较的延迟。 - 握手协议:请求和应答信号的路径需要设false path,但数据路径需要根据协议要求约束。
# 两级同步器 set_false_path -from [get_pins sync1_reg/Q] -to [get_pins sync2_reg/D] # 异步FIFO指针比较 set_max_delay 2.0 -from [get_pins fifo_wptr_reg/Q] -to [get_pins fifo_full_comb/*]异步FIFO的指针比较逻辑不能用false path,因为指针比较的结果直接影响full和empty信号的正确性。用set_max_delay限制比较逻辑的延迟,确保在下一个时钟沿之前完成比较。
5.4 低功耗设计对时序的影响
如果芯片用了时钟门控(clock gating)或多电压域(multi-voltage domain),时序分析会更复杂。
时钟门控单元会引入额外的时钟延迟,需要在SDC里用set_clock_gating_check约束:
set_clock_gating_check -setup 0.1 -hold 0.05 [get_cells gating_inst]多电压域之间的信号需要电平转换器(level shifter),这些转换器有额外的延迟,需要在时序分析里考虑。如果SDC里没有正确约束电压域边界,PrimeTime会按单电压域分析,导致slack偏乐观。
实操心得:低功耗设计的时序分析,建议在SDC里用
set_voltage命令显式声明电压域,让PrimeTime按实际电压查表。如果库文件里没有对应电压的时序表,工具会报warning,这时候需要检查库文件是否完整。
5.5 时序分析的经验法则
最后分享几条我在实际项目中总结的经验法则:
法则一:约束宁紧勿松。综合阶段uncertainty设大一点,后端收紧。如果反过来,综合乐观、后端悲观,会导致后端反复迭代,浪费时间。
法则二:false path宁少勿多。每加一条false path,都要问自己:这条路径真的不需要检查吗?如果答案是"可能不需要",那就先不设,等确认了再设。
法则三:multicycle path必须成对出现。setup和hold一起写,缺一不可。写完用report_exceptions确认两条都生效了。
法则四:I/O约束必须来自datasheet。不要凭感觉写input_delay和output_delay,这些值必须来自外部器件的时序规格。写错了,芯片和外部器件通信就会出问题。
法则五:时序报告要存档。每次迭代的WNS、TNS、NVP都记录下来,形成趋势图。如果某次优化后指标突然变差,能快速定位是哪次改动引入的。
时序路径分析这件事,说到底就是"把正确的约束给正确的工具,然后相信工具的结果"。约束是人的判断,工具是执行者。判断错了,工具再强也没用。我见过太多项目因为SDC写错导致流片失败,也见过因为约束过度悲观导致面积翻倍。这门手艺没有捷径,就是多看报告、多踩坑、多总结。下一篇我会讲PrimeTime的高级分析技巧,包括OCV(片上偏差)分析、POCV(参数化片上偏差)分析和时序ECO的具体操作,感兴趣的话可以继续关注。