4、逻辑综合入门:DC(Design Compiler)基本流程、读取RTL与约束、综合策略(Top-down vs Bottom-up)

好,咱们进入第四讲。逻辑综合,说白了就是把RTL代码翻译成门级网表。这一步是衔接前端设计和后端物理实现的桥梁。我个人觉得,这是整个流程里最需要「感觉」的一环——你不仅要懂代码,还得懂工艺库,懂时序。

今天咱们就聊聊DC(Design Compiler)的基本玩法。我会把流程拆开,一步步讲清楚。

4.1 DC基本流程:三步走

DC的流程,我习惯分成三步:

  1. 读入设计——把RTL代码和库文件加载进来
  2. 施加约束——告诉工具你的芯片要跑多快、功耗多少
  3. 综合优化——工具根据约束把RTL映射到门级网表

嗯,听起来简单。但每一步都有坑。我曾经在一个项目里,因为库文件路径配错了,综合出来的网表面积大了30%。排查了两天才找到原因。所以,每一步都要仔细。

4.2 读取RTL与约束

4.2.1 读取RTL

DC支持多种读入方式。我个人最常用的是read_verilogread_vhdl。举个例子:

# 设置库路径
set target_library "typical.db"
set link_library "* typical.db"
set symbol_library "generic.sdb"

# 读入RTL
read_verilog {top.v sub_module1.v sub_module2.v}
# 或者用 analyze & elaborate
analyze -format verilog {top.v sub_module1.v sub_module2.v}
elaborate top

这里有个细节:read_verilog是直接读,而analyze + elaborate会做语法检查和模块解析。我建议新手用后者,因为报错信息更友好。你想想看,如果代码里有个端口没对上,read_verilog可能直接崩掉,而elaborate会告诉你具体是哪个模块、哪个信号出了问题。

小技巧: 读入后记得用 link 命令检查一下模块是否全部解析成功。如果出现 "Unresolved reference" 的警告,说明有模块没找到,赶紧回头检查。

4.2.2 施加约束

约束是综合的灵魂。没有约束,工具就不知道往哪个方向优化。我见过不少新手,上来就写 create_clock -period 10 [get_ports clk],然后就不管了。这其实远远不够。

一个完整的约束文件,至少包含以下几类:

约束类型 常用命令 说明
时钟约束 create_clock, set_clock_uncertainty 定义时钟周期、占空比、抖动
输入延迟 set_input_delay 外部信号到达芯片引脚的时间
输出延迟 set_output_delay 芯片输出到外部寄存器的建立时间要求
伪路径 set_false_path 告诉工具某些路径不需要时序检查
多周期路径 set_multicycle_path 某些路径需要多个时钟周期才能稳定

举个例子,一个典型的约束文件长这样:

# 时钟约束
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_clock_uncertainty -hold 0.1 [get_clocks clk]

# 输入延迟
set_input_delay -clock clk -max 3 [get_ports data_in]
set_input_delay -clock clk -min 1 [get_ports data_in]

# 输出延迟
set_output_delay -clock clk -max 4 [get_ports data_out]
set_output_delay -clock clk -min 1 [get_ports data_out]

# 伪路径:跨时钟域
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
注意: 约束不是越紧越好。我曾经在一个项目里,把输入延迟设得太小,结果工具为了满足时序,疯狂插入缓冲器,面积直接爆了。后来放宽了0.5ns,面积降了15%,时序反而更好。所以,约束要合理,要跟后端团队对齐。

4.3 综合策略:Top-down vs Bottom-up

综合策略的选择,直接影响项目进度和结果质量。我经历过两种策略的优劣,这里分享一下。

4.3.1 Top-down 综合

Top-down,就是把整个芯片当作一个整体,一次性综合。DC会从顶层开始,自动处理所有子模块。

优点:

  • 全局优化效果好——工具能看到所有模块之间的交互
  • 脚本简单——只需要一个约束文件,一个脚本
  • 适合小规模设计(百万门以内)

缺点:

  • 运行时间长——设计越大,综合越慢
  • 内存消耗大——DC需要把整个设计加载到内存
  • 调试困难——如果某个子模块出问题,很难定位

我记得有一次,一个同事用Top-down综合一个500万门的设计,跑了整整三天。结果因为一个约束写错了,全部重来。嗯,那滋味不好受。

4.3.2 Bottom-up 综合

Bottom-up,就是先综合每个子模块,生成各自的网表,然后再把网表拼起来做顶层综合。

优点:

  • 并行度高——多个模块可以同时综合,节省时间
  • 内存占用小——每次只处理一个模块
  • 调试方便——哪个模块出问题,单独修就行

缺点:

  • 全局优化受限——工具看不到跨模块的路径
  • 脚本复杂——需要为每个模块写单独的约束和脚本
  • 接口约束要精确——子模块的输入输出延迟必须算准

我个人的习惯是:小设计用Top-down,大设计用Bottom-up。具体来说,如果设计规模超过200万门,或者有多个时钟域、多个电压域,我会优先考虑Bottom-up。

实战建议: 如果你用Bottom-up,一定要在子模块综合时留出余量。比如顶层要求时钟周期10ns,子模块综合时按9ns约束。这样拼起来后,顶层还有1ns的余量去处理连线延迟。我曾经因为没留余量,顶层综合时时序一片红,最后不得不重新综合所有子模块,浪费了一周时间。

4.4 综合后的检查

综合完成后,别急着往后端送。先做几个检查:

  1. 时序报告——看有没有违例路径,特别是建立时间和保持时间
  2. 面积报告——看总面积是否在预算内
  3. 功耗报告——看动态功耗和静态功耗是否达标
  4. 网表检查——用 check_design 命令检查网表是否有悬空、短路等问题

我每次都会跑一遍 report_qor,这个命令会汇总所有关键指标。如果看到某个指标异常,比如面积突然比预期大了20%,我会立刻回头查约束和代码。

4.5 小结

逻辑综合,说白了就是「翻译+优化」。翻译不难,难的是优化。而优化的关键,在于约束是否合理、策略是否得当。

我建议你从一个小模块开始练手,比如一个简单的SPI接口。先跑通Top-down流程,再试试Bottom-up。等把DC的脚本写熟了,你会发现,综合其实没那么神秘。

下一讲,咱们聊聊综合后的网表如何做形式验证。嗯,那又是一个容易踩坑的地方。