4. 综合进阶:时序约束(SDC)编写、综合优化技巧、面积与速度的权衡、综合报告解读
综合这一步,说白了就是把 RTL 代码翻译成门级网表。但光翻译出来没用,你得告诉工具——「我想要什么样的芯片」。这个「告诉工具」的过程,就是写 SDC。
我刚开始做后端时,总觉得 SDC 是前端的事。后来有一次,综合出来的网表时序惨不忍睹,前端说「我约束写好了」,结果一查,时钟定义错了。嗯,从那以后,我都是自己亲手过一遍 SDC。
4.1 SDC 到底在约束什么?
SDC 的全称是 Synopsys Design Constraints。它定义了芯片的时序环境。工具根据 SDC 来优化电路。
核心约束就这几类:
- 时钟定义:频率、波形、时钟组
- 输入输出延迟:信号从片外来、往片外去的 timing budget
- 时序例外:false path、multicycle path
- 设计规则:max_transition、max_capacitance、max_fanout
核心原则:SDC 要「紧」但不要「死」。太松了工具不优化,太紧了工具过度优化导致面积爆炸。
4.2 时钟约束——最基础也最容易翻车
时钟是时序的脉搏。脉搏乱了,整个芯片就乱了。
# 创建一个 100MHz 的时钟,占空比 50%
create_clock -name clk -period 10.0 [get_ports clk]
# 生成时钟(分频时钟)
create_generated_clock -name clk_div2 \
-source [get_ports clk] -divide_by 2 \
[get_pins u_div/clk_out]
我见过最典型的翻车案例:有人把 create_clock 写成了 create_generated_clock,结果综合工具认为两个时钟完全异步,跨时钟域的路径全被忽略。流片回来,跨时钟域的数据采样出错。
注意:生成时钟的 source 必须是时钟源 pin,不是 clock port。写错了工具不会报错,但时序分析会漏掉路径。
4.3 输入输出延迟——把芯片放进系统里看
芯片不是孤岛。你得告诉工具:片外信号什么时候来,片内信号什么时候要走。
# 输入延迟:片外信号在时钟沿后 2ns 到达片内
set_input_delay -clock clk -max 2.0 [get_ports data_in]
# 输出延迟:片内信号需要在时钟沿前 3ns 到达片外
set_output_delay -clock clk -max 3.0 [get_ports data_out]
这里有个坑:set_input_delay 是相对于时钟沿的延迟。如果写反了符号,工具会认为你有 8ns 的 slack,实际上根本跑不到 100MHz。
我的习惯:先画一张时序图,把时钟沿、数据窗口、输入输出延迟都标出来。画清楚了再写 SDC,基本不会错。
4.4 时序例外——别让工具做无用功
不是所有路径都需要在一个时钟周期内完成。有些路径天生就是假的,有些路径需要多个周期。
# False path:异步时钟域之间的路径
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# Multicycle path:数据需要 2 个周期才能稳定
set_multicycle_path -setup 2 -from [get_pins u_slow/data_reg/CK] \
-to [get_pins u_slow/data_reg/D]
为什么要设 false path?你想想看,两个异步时钟域之间的路径,工具非要把它优化到 10ns 以内,结果就是面积白白增加,时序还未必能收敛。
我曾经接手一个项目,综合出来的面积比预期大了 30%。查了半天,发现是跨时钟域的路径没设 false path,工具把所有力气都花在了不可能收敛的路径上。
4.5 综合优化技巧——让工具听你的话
综合工具不是万能的。你得告诉它哪些路径重要,哪些路径可以牺牲。
| 优化方向 | 命令/策略 | 适用场景 |
|---|---|---|
| 面积优先 | set_max_area 0 |
成本敏感、时序宽松 |
| 速度优先 | group_path -name critical -weight 5 |
高频模块、关键路径 |
| 功耗优先 | set_max_dynamic_power |
移动设备、低功耗场景 |
| 平衡策略 | 默认设置 + 手动调整权重 | 大多数项目 |
我个人习惯的做法是:先跑一版默认策略,看哪些路径是瓶颈。然后针对瓶颈路径单独设 group_path,把权重调高。这样不会全局过度优化。
4.6 面积与速度的权衡——没有免费的午餐
这是数字后端永恒的话题。面积小、速度快、功耗低——三者最多取其二。
为什么面积和速度会冲突?说白了,速度快的电路需要更大的驱动能力,更大的驱动能力意味着更大的晶体管,更大的晶体管就是更大的面积。
- 逻辑复制:把一个大扇出的逻辑复制成多份,减少负载,提升速度。代价是面积翻倍。
- 单元升级:把低驱动单元换成高驱动单元,速度提升 10%,面积增加 20%。
- 路径重定时:把组合逻辑在寄存器之间重新分配,不增加面积但可能影响布局。
经验法则:综合阶段不要追求极限速度。留 10%-15% 的时序余量给后端布局布线。否则后端一插时钟树,时序就崩了。
4.7 综合报告解读——别只看 slack
综合跑完了,工具会吐一堆报告。很多人只看 slack 是不是正的,其实远远不够。
我一般会看这几份报告:
- 时序报告:最差路径的 slack、路径深度、单元类型
- 面积报告:组合逻辑面积、时序逻辑面积、物理面积
- 功耗报告:动态功耗、静态功耗、漏电功耗
- 设计规则报告:max_transition、max_capacitance 违规
- 利用率报告:标准单元利用率、宏单元利用率
举个例子,如果时序报告显示最差路径的 slack 是 -0.5ns,但路径深度只有 10 级,那说明单元驱动能力不够。如果路径深度有 30 级,那说明逻辑级数太多,需要改 RTL。
避坑指南:我曾经只看 slack 为正就认为综合通过了。结果后端布局布线后,时序全面崩溃。原因是综合时没设 set_max_transition,工具用了超大驱动单元,后端根本布不通。
4.8 综合流程中的 SDC 检查清单
每次综合前,我都会过一遍这个清单:
| 检查项 | 检查内容 | 常见问题 |
|---|---|---|
| 时钟完整性 | 所有时钟是否正确定义? | 漏定义生成时钟 |
| IO 约束 | 输入输出延迟是否合理? | 符号写反、数值过大 |
| 时序例外 | 跨时钟域是否设 false path? | 漏设导致面积爆炸 |
| 设计规则 | max_transition 是否设置? | 未设导致后端无法收敛 |
| 时钟组 | 异步时钟是否分到不同组? | 同组导致过度约束 |
嗯,这个清单我用了好几年,每次都能帮我发现一两个遗漏。
4.9 综合进阶知识体系
下面这张图总结了本章的核心脉络:
从这张图可以看得很清楚:SDC 约束是输入,综合引擎根据约束做优化,优化过程中需要在面积、速度、功耗之间做权衡,最后通过报告来验证结果。
每一步都有坑,每一步都有技巧。但只要你把基础打牢了,这些都不是问题。
最后说一句:综合是后端流程的第一步,也是最重要的一步。综合做不好,后面布局布线再怎么折腾也救不回来。花时间把 SDC 写对、把优化策略调好,后面会省很多事。