第三讲:逻辑综合基础(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_designlink这两步。我见过有人漏了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