第二章 逻辑综合:DC/Genus工具实战,掌握时序约束与面积优化的平衡艺术
逻辑综合,说白了就是把RTL代码翻译成门级网表。这一步做得好不好,直接决定了芯片能不能跑起来、能跑多快、要花多少钱。
我刚开始做综合时,总觉得这是工具的活儿,跟我没啥关系。直到有一次,我写了个挺复杂的模块,DC跑出来面积大得吓人,时序还一塌糊涂。后来才发现,是代码风格和约束写法都有问题。从那以后,我再也不敢小看综合这一步了。
2.1 综合工具的核心流程
不管是Synopsys的DC还是Cadence的Genus,核心流程其实差不多。你想想看,工具要做的无非就是三件事:读入设计、施加约束、优化输出。
综合三阶段:
- 转换(Translation):把RTL代码读入工具内部数据库,生成GTECH网表
- 逻辑优化(Logic Optimization):对布尔逻辑进行化简、重写、资源共享
- 映射(Mapping):把优化后的逻辑映射到目标工艺库的标准单元上
我个人习惯把综合脚本分成三个文件:setup.tcl(环境配置)、constraints.tcl(时序约束)、run.tcl(综合流程)。这样改起来方便,也容易复用。
2.2 时序约束:别让工具猜你的心思
工具不会读心术。你不给它约束,它就按最保守的方式做,结果往往不是你想要的。
我在项目中遇到过最典型的问题:一个模块综合出来时序报告全是violation,但仿真明明没问题。后来发现是约束文件里忘了设input delay,工具默认输入信号在时钟沿才到,这谁受得了?
2.2.1 核心约束命令
| 约束类型 | 命令示例 | 说明 |
|---|---|---|
| 时钟定义 | create_clock -period 10 [get_ports clk] |
周期10ns,对应100MHz |
| 输入延迟 | set_input_delay -max 3 [get_ports data_in] |
数据在时钟沿后3ns内到达 |
| 输出延迟 | set_output_delay -max 4 [get_ports data_out] |
数据在时钟沿前4ns必须稳定 |
| 伪路径 | set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] |
跨时钟域路径不做时序检查 |
| 多周期路径 | set_multicycle_path 2 -setup -from [get_pins reg_a/CK] |
数据需要两个时钟周期才能稳定 |
我的小技巧:约束文件里,时钟定义一定要放在最前面。因为input delay、output delay都依赖时钟做参考。我曾经见过有人把约束写反了顺序,结果工具报了一堆莫名其妙的warning。
2.2.2 时序报告怎么看
综合跑完后,第一件事就是看时序报告。别急着看violation,先确认时钟定义对不对。
*******************************************************************************
Report : timing
-path_type full
-delay_type max
-max_paths 10
*******************************************************************************
Startpoint: data_reg[0]/CK
Endpoint: result_reg[7]/D
Path Group: clk
Path Type: max
Point Incr Path
---------------------------------------------------------------
clock clk (rise edge) 0.00 0.00
clock network delay (propagated) 0.50 0.50
data_reg[0]/CK (DFF) 0.00 0.50 r
data_reg[0]/Q (DFF) 0.35 0.85 r
U1/A (AND2) 0.12 0.97 r
U1/Z (AND2) 0.18 1.15 r
result_reg[7]/D (DFF) 0.00 1.15 r
data arrival time 1.15
clock clk (rise edge) 10.00 10.00
clock network delay (propagated) 0.50 10.50
result_reg[7]/CK (DFF) 10.50 r
library setup time -0.20 10.30
data required time 10.30
---------------------------------------------------------------
data required time 10.30
data arrival time 1.15
---------------------------------------------------------------
slack (MET) 9.15
*******************************************************************************
你看这个报告,slack是9.15ns,说明时序余量很充裕。但如果slack是负的,比如-0.5ns,那就得想办法优化了。
注意:slack为正不代表设计没问题。我曾经遇到一个设计,综合报告全是绿色,但流片回来就是跑不到目标频率。后来发现是约束里忘了设clock uncertainty,工具把理想时钟当成了真实时钟。所以,clock uncertainty一定要加,通常设周期5%~10%。
2.3 面积优化:别让芯片变成奢侈品
面积就是钱。同样的功能,面积越小,成本越低。但面积和时序往往是矛盾的——你越想跑得快,就得用更大的cell,面积自然就上去了。
2.3.1 面积优化的常用手段
- 资源共享:多个运算共用同一个硬件模块,比如两个加法器合并成一个
- 操作数重排:把关键路径上的操作提前,减少逻辑级数
- 门控时钟:不用的寄存器关掉时钟,既省面积又省功耗
- 工艺库选择:用低驱动强度的cell代替高驱动强度的cell
我建议你在综合脚本里加上 set_ultra_optimization -area(DC)或 set_db area_weight 1.0(Genus),让工具在优化时多考虑面积因素。
2.4 时序与面积的平衡艺术
说白了,这就是个trade-off。你不可能既要马儿跑得快,又要马儿不吃草。
我个人习惯的做法是:
- 先跑一版不加任何面积优化,看看时序能不能收敛
- 如果时序有violation,先优化代码和约束,而不是急着调工具参数
- 时序收敛后,再打开面积优化选项,逐步收紧面积目标
- 每次只改一个参数,对比前后结果,记录变化
平衡策略总结:
| 场景 | 优先目标 | 推荐做法 |
|---|---|---|
| 高频设计(>500MHz) | 时序优先 | 使用高驱动cell,关闭面积优化 |
| 低成本设计 | 面积优先 | 开启资源共享,使用低驱动cell |
| 一般设计(100~500MHz) | 平衡 | 先满足时序,再逐步收紧面积 |
2.5 避坑指南
做综合这些年,我踩过的坑不少。挑几个典型的说说:
- 忘记设wire load model:工具会默认用理想连线,综合结果跟实际差很远。我吃过这个亏,后来每次都在脚本里显式指定。
- 约束过紧:有些人为了保险,把约束设得特别严。结果工具为了满足时序,用了大量大cell,面积爆炸。其实留10%~15%的余量就够了。
- 忽略transition time:综合报告里时序都过了,但后仿就是出问题。后来发现是某个信号的transition time太大,导致下一级cell延迟剧增。记得加
set_max_transition。 - 综合后不看log:工具在log里会报很多warning,比如unresolved reference、inferred latch。我建议你养成习惯,每次综合完先扫一遍log,别等流片回来才发现问题。
一个实用脚本片段:
# 设置时钟
create_clock -period 10 [get_ports clk]
set_clock_uncertainty 0.5 [get_clocks clk]
# 设置输入输出延迟
set_input_delay -max 3 [all_inputs] -clock clk
set_output_delay -max 4 [all_outputs] -clock clk
# 设置transition和load
set_max_transition 0.5 [current_design]
set_max_capacitance 0.3 [current_design]
# 综合策略
compile_ultra -no_autoungroup -retime
嗯,综合这一步,说难不难,说简单也不简单。关键是要理解工具在做什么,而不是盲目跑脚本。你想想看,工具只是按你的约束和代码来干活,真正决定芯片质量的,还是你写的代码和约束的质量。