第三讲:逻辑综合基础(Design Compiler)
各位同学好,今天我们聊聊逻辑综合。说实话,很多刚入行的朋友觉得综合就是跑个脚本,没什么技术含量。我当年也这么想,直到有一次项目里综合出来的面积比预期大了30%,后端同事差点没把我骂死……嗯,从那以后我才真正重视起综合这个环节。
一、综合的概念与目标
逻辑综合,说白了就是把RTL代码翻译成门级网表。你写的是行为级的描述,比如always @(posedge clk),但芯片里实际跑的是一个个标准单元——与门、或门、触发器这些东西。综合就是做这个翻译工作的。
综合有三个核心目标,我习惯叫它「三角平衡」:
| 目标 | 说明 | 我踩过的坑 |
|---|---|---|
| 时序(Timing) | 电路能跑多快,能不能满足时钟频率要求 | 有一次为了省面积,把关键路径的驱动强度调小了,结果setup违例一片红 |
| 面积(Area) | 芯片占多大地方,直接决定成本 | 某项目为了赶时序,疯狂加buffer,最后面积超标,流片成本翻倍 |
| 功耗(Power) | 芯片发热和电池续航 | 低功耗设计里,综合阶段没做clock gating,后端加回来费了老大劲 |
这三个目标往往是互相矛盾的。你想跑得快,就得用大驱动单元,面积就大了;你想省电,就得降低电压,时序又变差了。综合工程师的工作,就是在这三者之间找到最优解。
核心观点:综合不是「一键生成」的傻瓜工具。你给DC什么约束,它就给你什么结果。约束写得好,综合结果就好;约束写得烂,后端再怎么优化也救不回来。
二、DC的输入输出文件
Design Compiler的输入输出,说白了就是「吃进去RTL,拉出来网表」。但中间涉及的文件类型还挺多的,我一个个说。
2.1 输入文件
(1)RTL代码(.v / .sv)
这是你的设计源文件。我个人习惯把所有RTL文件列在一个文件列表里,用-f参数读入,这样脚本干净很多。你想想看,如果每个文件都单独写一行read_verilog,脚本会变得又臭又长。
(2)工艺库文件(.lib)
这是代工厂提供的标准单元库。里面记录了每个单元的延迟、面积、功耗、驱动能力等信息。DC就是靠这些数据来计算时序和面积的。
我记得第一次接触.lib文件时,打开一看好几万行,直接懵了。其实你不用全看懂,只需要知道几个关键参数:
cell_footprint:单元的功能类型timing:单元的延迟信息power:单元的功耗信息area:单元的面积
(3)时序约束文件(.sdc)
这是综合的灵魂文件。你告诉DC:我的时钟频率是多少,哪些路径是假的,哪些路径需要特别优化。SDC的写法直接决定了综合结果的好坏。
我的习惯:SDC文件我会分成三部分写——时钟定义、输入输出延迟、例外路径。这样后期调试时一目了然。
(4)其他输入文件
.db:Synopsys自己的库格式,是.lib的二进制版本,读起来更快.sdc:时序约束.upf:低功耗设计用的电源格式文件(如果做低功耗综合)
2.2 输出文件
(1)门级网表(.v / .vg)
这是综合最重要的输出。里面全是标准单元的实例化,比如AND2X1 U1 (.A(a), .B(b), .Y(c))。后端工具就靠这个网表来做布局布线。
(2)时序报告(.rpt)
DC会生成各种报告,告诉你哪些路径违例了,哪些路径还有余量。我每次综合完第一件事就是看report_timing,看看最差的那条路径能不能接受。
(3)面积报告(.area.rpt)
告诉你综合出来的电路用了多少面积。我习惯把面积报告和预估面积对比一下,如果差太多,说明约束或者代码有问题。
(4)其他输出文件
.sdf:标准延迟格式文件,用于后仿真.ddc:DC的数据库文件,可以保存综合后的设计状态.scan.v:如果做了DFT,会输出扫描链网表
注意:输出网表一定要检查有没有assign语句。DC默认可能会产生一些组合逻辑的assign,这在后端工具里容易出问题。我一般会在脚本里加一句set_fix_multiple_port_nets -all -buffer_constants来避免这个问题。
三、DC的启动与基本脚本结构
启动DC其实很简单,终端里敲dc_shell或者design_vision就行。前者是命令行模式,后者是图形界面。我个人习惯用命令行,因为可以写脚本批量跑,而且服务器上也没法开图形界面。
3.1 启动方式
| 命令 | 说明 | 适用场景 |
|---|---|---|
dc_shell |
命令行模式 | 批量跑综合、服务器环境 |
design_vision |
图形界面模式 | 调试、看电路结构 |
dc_shell -f run.tcl |
批处理模式 | 自动化流程 |
3.2 基本脚本结构
一个标准的DC综合脚本,我一般分成四个部分。你照着这个结构写,基本不会出大问题。
# ============================================
# 第一部分:设置库文件
# ============================================
set target_library "typical.lib"
set link_library "* typical.lib"
set symbol_library "typical.sdb"
# ============================================
# 第二部分:读入设计
# ============================================
read_verilog {top.v module_a.v module_b.v}
current_design top
link
# ============================================
# 第三部分:施加约束
# ============================================
source -echo top.sdc
# 时钟约束示例
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_input_delay -max 2.0 -clock clk [get_ports data_in]
set_output_delay -max 1.5 -clock clk [get_ports data_out]
# ============================================
# 第四部分:综合与输出
# ============================================
compile_ultra
# 输出网表
write -format verilog -hierarchy -output ./output/top_synth.v
# 输出报告
report_timing > ./output/timing.rpt
report_area > ./output/area.rpt
report_power > ./output/power.rpt
我的建议:脚本里一定要加current_design和link这两步。我见过有人漏了link,结果综合出来的网表里全是空的模块,查了半天才发现是没链接库。
3.3 常用命令速查
刚开始用DC,你不需要记住所有命令。下面这几个是最高频的:
check_design:检查设计有没有语法错误或悬空端口report_timing:看时序报告report_area:看面积报告report_power:看功耗报告compile_ultra:启动综合(比compile优化效果更好)write -f verilog:输出网表
避坑指南:我曾经在综合一个大型SoC时,忘了设置set_clock_uncertainty,结果综合出来的电路在后端时序收敛时怎么都过不了。后来才发现,综合时没留余量,后端稍微有点偏差就违例了。所以时钟不确定性一定要设,一般留5%~10%的余量比较保险。
四、小结
逻辑综合是数字芯片设计里承上启下的关键环节。你写RTL时觉得「差不多就行了」,综合结果会诚实地告诉你「差得远」。我见过太多人把综合当成黑盒子,跑完脚本看都不看报告就扔给后端,结果后端同事天天加班修时序。
记住一句话:综合结果的好坏,90%取决于约束的质量。花时间把SDC写清楚、把报告看仔细,比盲目跑十遍综合有用得多。
下一讲我们会深入SDC的写法,包括时钟分组、虚假路径、多周期路径这些实战技巧。到时候我会拿一个真实项目里的例子来讲,保证你们听完就能用上。
公众号:蓝海资料掘金营,微信deep3321