第三章:逻辑综合基础——Synopsys DC综合流程、时序约束(SDC)编写、综合策略与优化、综合后网表检查

各位同学,欢迎来到第三章。

说实话,逻辑综合是整个数字后端流程里,我最喜欢的一步。为什么?因为这是你第一次把「人话」(RTL代码)翻译成「机器能懂的话」(门级网表)。这一步做得好,后面物理设计会顺很多;这一步要是埋了雷,那后面……嗯,你懂的。

3.1 Synopsys DC综合流程——别急着跑脚本

很多新手拿到DC,第一件事就是敲 dc_shell 然后开始跑。我建议你先停一下。

综合流程其实就三大步:

  1. 读入设计:把RTL、库文件、约束文件喂给DC。
  2. 编译:DC把RTL映射到目标工艺库上。
  3. 输出:生成网表、报告、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。先做三件事:

  1. 形式验证:用Formality比对RTL和网表。确保综合没改逻辑。
  2. 时序检查:看 report_timing 里的worst slack。我一般要求setup slack > 0.3ns,hold slack > 0.05ns。
  3. 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阶段一一兑现。所以,别急,慢慢来。