一、逻辑综合是什么
1. 逻辑综合的定义
逻辑综合是指将硬件描述语言编写的 RTL 代码转换成门级网表的过程。
常见的硬件描述语言包括:
- VHDL
- SystemVerilog
- Verilog
综合前的 RTL 代码主要描述电路功能;综合后的门级网表则由具体标准单元库中的逻辑门、触发器、多路选择器等单元组成。
2. 为什么要做逻辑综合
RTL 代码本身并没有直接和制造工艺绑定。
经过逻辑综合之后,设计会被映射到具体工艺库中的标准单元上,从而生成后端物理设计可以继续使用的门级网表。
逻辑综合不仅仅是代码转换,还包括对电路进行优化和平衡,使设计尽量满足以下目标:
- 功能正确
- 速度满足时序要求
- 面积尽量小
- 功耗尽量低
这些目标通常合称为:
1 | PPA = Performance + Power + Area |
也就是:
- Performance:性能 / 速度
- Power:功耗
- Area:面积
3. 什么时候做逻辑综合
逻辑综合一般发生在前端设计完成到一定程度之后。
当 RTL 功能设计基本完成,并且准备进入后端物理设计阶段时,就需要进行逻辑综合。
因此,在一些公司或流程中,逻辑综合也会被称为“中端设计”。
整体流程可以简单理解为:
1 | RTL 设计 |
二、Design Compiler 基本使用方法
Design Compiler,简称 DC,是常用的逻辑综合工具。
1. 打开 DC
在终端输入:
1 | dc_shell |
即可进入 DC 的交互式命令环境。
2. 直接执行 TCL 脚本
如果已经写好了完整的综合脚本,可以使用下面的命令运行:
1 | dc_shell -f syn.tcl | tee dc.log |
含义说明:
| 命令部分 | 作用 |
|---|---|
dc_shell |
启动 DC |
-f syn.tcl |
执行指定的 TCL 脚本 |
tee dc.log |
将终端输出同时保存到 dc.log 文件中 |
这样做的好处是:
- 可以自动执行完整综合流程
- 可以保存综合日志
- 方便后续查看报错和 warning
三、DC 查询命令的使用
1. 查询某个命令是否存在
可以使用:
1 | help *命令关键字* |
例如:
1 | help *clock* |
该命令可以查询和 clock 相关的 DC 命令。
2. 查看某个命令的详细用法
可以使用:
1 | man 命令名 |
例如:
1 | man create_clock |
该命令会显示 create_clock 的详细说明、参数和使用方法。
3. 退出帮助页面
如果查看帮助时出现很多内容,并且屏幕下方显示:
1 | --More-- |
此时可以按:
1 | q |
退出当前帮助页面。
四、TCL 综合脚本示例
下面是一个简单的 DC 逻辑综合 TCL 脚本。
1 | #------------------------------ |
五、脚本关键命令解释
1. 设置设计名称
1 | set DESIGN_NAME my_module |
表示当前要综合的顶层模块名是 my_module。
这个名字通常要和 Verilog / SystemVerilog 代码中的顶层模块名一致。
2. 设置运行标签
1 | set TAG justrun |
TAG 用来区分不同综合结果的输出目录。
例如:
1 | ./results/justrun |
后续如果做多次实验,可以改成:
1 | set TAG run_1 |
这样方便管理不同版本的综合结果。
3. 设置 CPU 核数
1 | set_host_options -max_cores 8 |
表示 DC 最多使用 8 个 CPU 核心进行综合。
核数设置得合理可以提高运行速度。
4. 定义 WORK 库
1 | define_design_lib WORK -path ./WORK |
WORK 是 DC 用来存放中间分析结果的设计库。
如果不定义,工具可能无法正确保存和管理中间文件。
5. 设置结果目录
1 | set RESULTS_DIR ./results/${TAG} |
这段代码的作用是:
- 设置综合结果保存路径
- 如果目录不存在,就自动创建目录
6. 设置搜索路径
1 | set_app_var search_path "/tools/Lib/digit_lib/smic180/std/synopsys" |
search_path 用来告诉 DC 去哪里查找库文件、RTL 文件或其他相关文件。
设置之后,后续使用库文件时可以直接写文件名,不一定要写完整绝对路径。
7. 设置目标库
1 | set_app_var target_library "slow.db" |
target_library 是综合时用于映射的目标标准单元库。
简单理解:
1 | RTL 代码中的逻辑 |
例如:
- 与门
- 或门
- 非门
- 触发器
- 多路选择器
都会从 slow.db 这个库中选择对应单元。
8. 设置链接库
1 | set_app_var link_path "* slow.db" |
link_path 用来告诉 DC 在链接设计时可以去哪些库中寻找模块或标准单元。
其中:
1 | * |
表示当前已经读入 DC 的设计。
slow.db 表示标准单元库。
所以这句话可以理解为:
1 | 链接时,既要查找当前设计,也要查找 slow.db 标准单元库。 |
9. 读取 RTL 文件
1 | read_file -format sverilog "./sources/${DESIGN_NAME}.sv" |
表示以 SystemVerilog 格式读取 RTL 文件。
如果:
1 | set DESIGN_NAME my_module |
那么实际读取的文件就是:
1 | ./sources/my_module.sv |
10. 设置当前设计
1 | current_design ${DESIGN_NAME} |
告诉 DC 当前正在处理的顶层设计是哪一个。
11. 链接设计
1 | link |
link 的作用是将 RTL 设计和库文件连接起来。
如果设计中例化了子模块,或者需要使用标准单元库,link 就会检查这些模块和单元能不能找到。
如果找不到,就可能出现 unresolved reference 之类的错误。
12. 输出综合前文件
1 | write_file -hierarchy -format ddc -output ${RESULTS_DIR}/${DESIGN_NAME}.precompile.ddc |
这两条命令用于保存综合前的设计状态。
其中:
| 文件 | 作用 |
|---|---|
.ddc |
DC 自己使用的二进制设计文件 |
.v |
Verilog 格式文件 |
13. 创建时钟约束
1 | create_clock -period 1.0 [get_ports clk] |
表示给端口 clk 创建一个周期为 1.0 ns 的时钟。
周期 1.0 ns 对应频率为:
1 | 1 / 1 ns = 1 GHz |
这个约束会告诉 DC:
1 | 电路中的寄存器到寄存器路径必须尽量在 1.0 ns 内完成。 |
14. 执行综合
1 | compile_ultra |
compile_ultra 是 DC 中常用的高强度综合优化命令。
它会根据约束对设计进行优化,主要目标包括:
- 修复时序
- 减小面积
- 优化逻辑结构
- 进行技术映射
15. 输出综合后文件
1 | write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.postcompile.v |
这两条命令用于输出综合后的结果。
此时的 Verilog 文件已经不再是纯 RTL,而是综合后的门级结构。
16. 修改命名规则
1 | change_names -rules verilog -hierarchy |
综合后的网表中可能会出现一些特殊命名,例如带有反斜杠的名字。
这些名字虽然 DC 可以识别,但后端工具读入时可能会不方便。
因此需要用 change_names 将名称转换成更规范的 Verilog 命名形式。
17. 输出最终门级网表
1 | write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.mapped.v |
其中:
1 | mapped.v |
通常就是最终交给后端 APR 工具使用的门级网表。
六、综合后的数据怎么看
综合后,DC 会输出一些统计信息。
例如寄存器统计:
1 | =============================================================================== |
可以看出:
| 字段 | 含义 |
|---|---|
| Register Name | 寄存器名称 |
| Type | 类型,例如 Flip-flop |
| Width | 位宽 |
| Bus | 是否是总线 |
| AR | 是否有异步复位 |
| AS | 是否有异步置位 |
| SR | 是否有同步复位 |
| SS | 是否有同步置位 |
例如:
1 | register_file_reg |
是一个宽度为 128 bit 的触发器寄存器。
七、MUX 统计信息
综合后还可能看到 MUX 相关统计,例如:
1 | Statistics for MUX_OPs |
这表示:
在 my_module 的第 32 行,DC 推断出了一个多路选择器。
具体含义如下:
| 字段 | 含义 |
|---|---|
| block name/line | 模块名和代码行号 |
| Inputs | MUX 输入数量 |
| Outputs | MUX 输出位宽 |
| # sel inputs | 选择信号位数 |
这一行:
1 | | my_module/32 | 4 | 32 | 2 | |
可以理解为:
1 | 在 my_module 第 32 行,推断出了一个 4 选 1、输出位宽为 32 bit 的多路选择器。 |
因为 4 选 1 MUX 需要 2 bit 选择信号:
1 | 2 bit 选择信号可以表示 4 种情况: |
八、一个最小 DC 综合流程总结
一个最基础的逻辑综合流程可以总结为:
1 | 1. 设置设计名称 |
对应的简化 TCL 流程为:
1 | set DESIGN_NAME my_module |
九、学习重点
这篇内容适合刚开始学习 DC 逻辑综合时入门。
重点需要掌握:
- 逻辑综合的作用
- RTL 和门级网表的区别
target_library和link_path的区别- 如何读取 RTL 文件
- 如何创建时钟约束
- 如何运行
compile_ultra - 为什么要输出
mapped.v - 如何简单查看综合报告中的寄存器和 MUX 信息
十、常见理解误区
误区 1:RTL 代码可以直接给后端使用
不准确。
后端 APR 一般需要的是门级网表,而不是 RTL 代码。
RTL 代码需要先经过逻辑综合,映射成标准单元之后,才能继续进入后端流程。
误区 2:设置了 target_library 就够了
不够。
target_library 只是告诉 DC 综合映射时使用哪个库。
但链接设计时,还需要通过 link_path 告诉 DC 去哪里找这些库和当前设计。
误区 3:postcompile.v 就一定是最终网表
不一定。
通常还需要执行:
1 | change_names -rules verilog -hierarchy |
然后再输出:
1 | mapped.v |
mapped.v 才更适合作为后端工具输入。
误区 4:只要能综合成功就说明设计没问题
不一定。
综合成功只说明工具生成了门级网表。
后面还需要继续检查:
- 时序是否满足
- 面积是否合理
- 功耗是否可接受
- 是否有 warning
- 是否有 unresolved reference
- 是否需要做形式验证
- 是否可以被后端工具正确读入
十一、建议补充的报告命令
实际做综合时,建议在脚本中加入一些报告输出命令,方便查看结果。
1 | report_timing > ${RESULTS_DIR}/timing.rpt |
这些报告的作用如下:
| 报告 | 作用 |
|---|---|
report_timing |
查看关键路径和时序 slack |
report_area |
查看面积 |
report_power |
查看功耗 |
report_qor |
查看综合整体质量 |
report_constraint |
查看约束是否满足 |
check_design |
检查设计是否存在结构或连接问题 |
十二、最终复习版总结
逻辑综合的核心目的可以概括为一句话:
把 RTL 代码转换成满足时序、面积、功耗要求的门级网表。
一个完整的 DC 入门综合流程大致是:
1 | 准备 RTL |
最需要重点理解的是:
target_library:综合映射时用哪个标准单元库link_path:链接设计时去哪里找当前设计和库单元create_clock:告诉工具时钟周期要求compile_ultra:执行综合优化mapped.v:最终交给后端使用的门级网表