第三章:逻辑综合基础——Synopsys DC综合流程、时序约束(SDC)编写、综合策略与优化、综合后网表检查
各位同学,欢迎来到第三章。
说实话,逻辑综合是整个数字后端流程里,我最喜欢的一步。为什么?因为这是你第一次把「人话」(RTL代码)翻译成「机器能懂的话」(门级网表)。这一步做得好,后面物理设计会顺很多;这一步要是埋了雷,那后面……嗯,你懂的。
3.1 Synopsys DC综合流程——别急着跑脚本
很多新手拿到DC,第一件事就是敲 dc_shell 然后开始跑。我建议你先停一下。
综合流程其实就三大步:
- 读入设计:把RTL、库文件、约束文件喂给DC。
- 编译:DC把RTL映射到目标工艺库上。
- 输出:生成网表、报告、SDC等。
我在28nm项目里,最常用的启动命令是这样的:
dc_shell -topographical_mode -f run.tcl | tee -i dc.log
注意那个 -topographical_mode。为什么加这个?因为28nm工艺下,线延迟占比已经很高了。不加这个模式,DC估算的延迟会偏乐观,后面PR阶段你会哭的。我吃过这个亏,真的。
核心流程脚本框架(我习惯这么写)
# 1. 设置库
set target_library "scc28nll_hvt.db scc28nll_rvt.db scc28nll_lvt.db"
set link_library "* $target_library"
set symbol_library "scc28nll.sdb"
# 2. 读入设计
analyze -format verilog {top.v sub1.v sub2.v}
elaborate top
# 3. 施加约束
source constraints.sdc
# 4. 选择综合策略
compile_ultra -gate_clock -no_autoungroup
# 5. 输出
write -format verilog -hierarchy -output top_synth.v
write_sdc -output top_synth.sdc
report_timing > timing.rpt
report_area > area.rpt
report_power > power.rpt
你看,就这么几行。但每一行背后都有讲究。比如 compile_ultra 里的 -gate_clock,我建议在28nm工艺下一定要加。因为门控时钟能省不少动态功耗,而且DC处理得比PR工具好。
3.2 时序约束(SDC)编写——这是门手艺活
SDC写得好不好,直接决定了综合结果靠不靠谱。说白了,你是在告诉DC:「嘿,这些路径很重要,你得给我优化好;那些路径无所谓,别浪费面积。」
最基本的,时钟约束:
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_clock_uncertainty -hold 0.05 [get_clocks clk]
set_clock_transition 0.15 [get_clocks clk]
这里有个坑。很多同学只设 set_clock_uncertainty 的setup值,hold值随便写个0.1完事。我曾经在一个项目里,hold uncertainty设得太松,结果DC综合出来的网表,hold slack全是负的。后面PR阶段修hold修到吐血。
注意: 28nm工艺下,hold uncertainty建议设为setup的1/4到1/3。别偷懒。
输入输出延迟也是重头戏:
set_input_delay -max 4.5 -clock clk [get_ports data_in]
set_input_delay -min 1.2 -clock clk [get_ports data_in]
set_output_delay -max 5.0 -clock clk [get_ports data_out]
set_output_delay -min 1.0 -clock clk [get_ports data_out]
你想想看,这些数字怎么来的?不是拍脑袋。你得跟前端设计、封装工程师对齐。我一般会留10%-15%的余量,因为后面PR阶段还会加一些不确定性。
3.3 综合策略与优化——别只会用默认设置
DC默认的编译策略,说实话,在28nm下不太够用。我常用的策略有三种:
| 策略 | 适用场景 | 我个人的经验 |
|---|---|---|
| compile_ultra | 高性能设计 | 面积会大一点,但时序好 |
| compile_ultra -retime | 流水线不平衡 | 慎用,可能会打乱你的设计意图 |
| compile -map_effort high | 面积敏感 | 时序要求不高时用 |
我一般先用 compile_ultra 跑一版,看看时序瓶颈在哪。然后针对性地加 group_path:
group_path -name INPUTS -from [all_inputs]
group_path -name OUTPUTS -to [all_outputs]
group_path -name CRITICAL -from [get_pins regA/Q] -to [get_pins regB/D]
为什么要分组?因为DC默认对所有路径一视同仁。但实际项目中,有些路径就是命根子,有些路径可以放一放。分组后,DC会优先优化你标记的路径。
小技巧: 在综合前,先跑一下 check_timing。这个命令会告诉你哪些路径没约束到。我每次必跑,因为漏约束是综合阶段最常见的错误之一。
3.4 综合后网表检查——别急着往下走
综合完拿到网表,别急着扔给ICC2或Innovus。先做三件事:
- 形式验证:用Formality比对RTL和网表。确保综合没改逻辑。
- 时序检查:看
report_timing里的worst slack。我一般要求setup slack > 0.3ns,hold slack > 0.05ns。 - DRC检查:用
check_design看看有没有悬空引脚、驱动强度不够等问题。
我记得有一次,综合完网表时序全绿,但形式验证报了几百个不匹配。查了半天,原来是某个子模块的 always @(posedge clk or negedge rst_n) 里,复位逻辑写成了异步复位同步释放,但DC综合时把复位树给优化掉了。从那以后,我每次综合完,第一件事就是跑Formality。
还有一个容易被忽略的点:检查时钟门控。用 report_clock_gating 看看门控覆盖率。28nm工艺下,我一般要求门控覆盖率 > 85%。如果低于这个数,说明你的RTL里有很多不必要的时钟翻转,功耗会很难看。
report_clock_gating -detail > clock_gating.rpt
最后,看一眼面积报告。不是看总面积,而是看cell count和utilization。如果utilization超过70%,我建议你回头跟前端商量一下,是不是有些模块可以合并。因为70%的utilization在PR阶段会很难绕线。
好了,这一章的内容就这些。逻辑综合说难不难,说简单也不简单。关键是多跑几个版本,对比着看。我每次做新项目,至少会跑三版综合:一版看时序极限,一版看面积最优,一版看功耗平衡。然后取中间值作为最终版本。
下一章,我们会进入物理设计阶段。到时候你会发现,综合阶段打下的基础,会在PR阶段一一兑现。所以,别急,慢慢来。