3. RTL到网表的综合过程:综合工具、时序约束、面积优化
说实话,综合这一步是RTL走向物理实现的“第一道鬼门关”。你写的代码再漂亮,综合工具不认,那也白搭。我个人习惯把综合看作“翻译+优化”——把你用Verilog/SV写的行为描述,翻译成跟工艺库绑定的门级网表,同时还要满足时序、面积、功耗这几个互相打架的需求。
3.1 综合工具:各家有各家的脾气
市面上主流的综合工具就那么几个,Synopsys Design Compiler(DC)用的人最多,Cadence Genus也有不少拥趸,Yosys则常见于开源/FPGA流程。我在项目中主要用DC,但有些小团队用Genus也跑得挺顺。
- Design Compiler(DC):行业标杆,支持最全的SDC约束,优化算法非常成熟。我遇到的大部分复杂ASIC都是用DC过的。
- Genus:相比DC,它更强调“并行综合”,对大设计跑得稍快一些,但脚本语法跟DC略有差异。
- Yosys:开源里最活跃的,配合arachne-pnr或nextpnr做FPGA布局布线。我实习时用过一次,写RTL时注意不要用太花哨的语法。
不管你用哪款,核心流程都差不多:读入RTL → 指定工艺库 → 添加时序约束 → 运行综合 → 输出网表+SDF。下面这张图能让你快速理解整体脉络。
3.2 时序约束:你给工具画个“框”
综合工具如果没约束,它就会乱来——面积给你往死里做,时序可能完全不管。我见过最惨的一次,同事忘了设输入延迟,结果综合出来等效频率比目标低了一半……后来花了三天重新跑。
时序约束用SDC(Synopsys Design Constraints)格式,核心就那么几条:
# 定义时钟
create_clock -name clk -period 10 [get_ports clk]
# 输入延迟
set_input_delay -clock clk -max 2.0 [get_ports data_in]
# 输出延迟
set_output_delay -clock clk -max 1.5 [get_ports data_out]
# 伪路径(跨时钟域等)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# 时钟组(异步时钟域)
set_clock_groups -asynchronous -group {clk_a} -group {clk_b}
这里我要强调一个坑:约束要尽量准确。我曾经为了偷懒把输入延迟从2ns改成3ns,觉得“多留点余量”,结果综合工具因为太保守,把逻辑深压到了一个极其奇怪的形状,修都修不回来。所以别乱加margin,合理就好。
3.3 时序优化的三大狠招
综合工具做时序优化主要靠三种手段,你必须理解它们,才知道怎么写RTL能让工具“舒服”。
| 优化手段 | 原理 | 适用场景 |
|---|---|---|
| 逻辑重组 | 改变运算树的拓扑结构,缩短关键路径 | 多级加法器/乘法器 |
| 寄存器重定时 | 移动寄存器位置平衡延迟 | 流水线级数不均匀时 |
| 门尺寸调整 | 使用驱动能力更大的标准单元 | 长互连线负载过大 |
嗯,举个例子:你写了个a + b + c + d,综合工具会把它重构成树形加法器,而不是链式加法。这样关键路径就从3个加法器延迟降到了2个。你想想看,是不是比你手写pipeline省事多了?
3.4 面积优化:别让芯片“膨胀”
咱们做芯片,成本就是面积。工具默认倾向于时序优先,如果你不干预,面积经常超标。我常用的面积优化指令:
# 设置面积优化力度
set_max_area 0
compile_ultra -area_high_effort_script
# 禁止自动插入大量缓冲
set_dont_use [get_lib_cells *BUF*]
set_fix_hold [all_clocks]
注意,set_max_area 0不是让面积真的为0,而是让工具尽量去压缩。配合compile_ultra里的面积优先选项,效果很明显。
3.5 综合报告怎么看?别只会看“绿勾”
综合结束后,工具会吐出好几份报告。我见过不少新人只扫一眼“Timing Met”就打勾走人,结果到后端突然发现内部hold不收敛。你得看这几项:
- 时序报告:不仅看setup/hold slack,还要看最差的几百条路径的分布。我曾经吃过一次亏:几十条路径slack都在-0.01ns以内,工具说是“可忽略”,但后端布局一跑直接变成-0.15ns。所以建议把约束收严5%~10%。
- 面积报告:注意组合逻辑和寄存器分别占多少。如果组合逻辑占比异常高,很可能你RTL写成了“巨无霸状态机”。
- 功耗报告:动态功耗主要看toggle rate。我建议在RTL仿真时抓取真实的翻转率(VCD),不要用默认0.1,那太假了。
3.6 综合中的常见坑
多半是因为你用了wire型变量做多个驱动,或者组合反馈环路。我曾经花了一整天排查一个“神秘信号”,最后发现是
assign x = a & b和assign x = c | d写在了两个地方,综合时直接报multidrive。
先检查一下约束里有没有遗漏的时钟,或者有没有误设
set_clock_uncertainty。我年轻时在一个项目里把uncertainty设了0.5ns,结果工具死活不满足,后来改成0.1ns就过了——其实后端根本不需要那么大裕量。