DC 逻辑综合脚本与日志分析整理
来源:用户粘贴文本整理
主题:Design Compiler 逻辑综合脚本、多时钟约束、库文件区别、check_design、dc.log分析
说明:本文档为学习笔记式整理,内容经过重新排版、归纳和补充,便于复习与查阅。
目录
- 一、示例综合脚本
- 二、多时钟注意点
- 三、
link_path中*的含义 - 四、
target_library与synthetic_library的区别 - 五、
analyze、elaborate、current_design、link的作用 - 六、
check_design的作用 - 七、两次
check_design的区别 - 八、常见 LINT 报告解析
- 九、
dc.log信息怎么看 - 十、建议使用的 grep 关键词
- 十一、总结
一、示例综合脚本
下面是一个较完整的 DC 逻辑综合脚本示例。
1 | #------------------------------ |
二、多时钟注意点
脚本中创建了 5 个时钟:
1 | create_clock -period 1.0 [get_ports clk] |
这里需要注意:多时钟设计不能只创建时钟,还要说明时钟之间的关系。
1. 如果这些时钟是异步时钟
如果 clk、clk1、clk2、clk3、clk4 之间完全独立,没有固定相位关系,也没有固定频率关系,那么应该加:
1 | set_clock_groups -asynchronous \ |
这样可以告诉 DC:
这些时钟之间是异步关系,不要分析它们之间的跨时钟路径。
否则 DC 可能会默认这些时钟之间存在时序关系,从而分析一些本来不应该分析的路径。
2. 如果 clk1 ~ clk4 是由 clk 分频或倍频得到的
如果 clk1、clk2、clk3、clk4 是由 clk 通过 PLL、分频器、门控或其他逻辑产生的,就不能简单写成异步时钟。
这时应该使用:
1 | create_generated_clock |
例如:
1 | create_generated_clock \ |
简单记忆:
| 时钟关系 | 推荐约束 |
|---|---|
| 完全独立、无固定关系 | set_clock_groups -asynchronous |
| 由主时钟分频 / 倍频产生 | create_generated_clock |
| 同源但不同相位 | 根据实际相位关系建模 |
| 跨时钟有 CDC 同步器 | 需要结合 CDC 设计和 false path / async group 处理 |
三、link_path 中 * 的含义
脚本中有:
1 | set_app_var link_path "* $target_library $synthetic_library" |
这里的 * 表示:
DC 当前内存中已经读入的设计。
也就是说,* 可以让 DC 在 link 的时候,不仅去库里面找 cell,也会在当前已经读入的 RTL 设计中寻找模块。
1. 为什么需要 *
例如你的顶层模块中例化了一个子模块:
1 | module my_top (...); |
如果 my_fifo 这个模块已经通过 analyze 读入 DC 内存,但是 link_path 里没有 *,DC 在链接时可能找不到它。
所以:
1 | set_app_var link_path "* $target_library $synthetic_library" |
可以理解为:
链接时,既要查找当前设计,也要查找标准单元库和综合库。
2. 简单记忆
1 | * :当前 DC 内存中的设计 |
四、target_library 与 synthetic_library 的区别
脚本中有:
1 | set_app_var target_library "svt_ssg_0p72v_m40c.db" |
这两类库的作用不同。
1. 总体区别
| 库变量 | 文件类型 | 主要作用 | 最终网表里会不会出现 |
|---|---|---|---|
target_library |
.db |
真实标准单元库,综合最终映射到这里面的 cell | 会 |
synthetic_library |
.sldb |
综合过程中的高级运算 / 虚拟库,用于优化加法器、乘法器、比较器等结构 | 一般不会直接出现 |
2. target_library:真实工艺库
1 | set_app_var target_library "svt_ssg_0p72v_m40c.db" |
这个 .db 文件是标准单元库,里面包含真实工艺下可用的 cell,例如:
AND2OR2NAND2NOR2INVDFFMUXAOIOAIBUF
DC 综合后,最终会把 RTL 逻辑映射成这个库里的标准单元。
例如 RTL 中写:
1 | assign y = a & b; |
综合后可能变成类似:
1 | AND2_X1 U1 ( |
这里的 AND2_X1 就来自 target_library。
3. synthetic_library:综合用的高级库
1 | set_app_var synthetic_library "standard.sldb dw_foundation.sldb" |
synthetic_library 不是具体工艺的标准单元库,而是 DC 用来识别和优化高级运算结构的库。
例如 RTL 中写:
1 | assign sum = a + b; |
DC 不一定一开始就直接用一堆 AND、OR、XOR 去硬拼,而是可能先识别为:
- 加法器
- 乘法器
- 比较器
- 移位器
- 除法器
这些高级结构可能先映射到 synthetic_library 里面的组件,然后再进一步综合成 target_library 里的真实标准单元。
4. standard.sldb 与 dw_foundation.sldb
| 库 | 作用 |
|---|---|
standard.sldb |
DC 自带的基础 synthetic library |
dw_foundation.sldb |
DesignWare 基础库,提供更丰富的加法器、乘法器、比较器、除法器等高级算术模块 |
例如:
1 | assign y = a * b; |
如果有 dw_foundation.sldb,DC 可能会调用 DesignWare 里的乘法器结构,优化效果通常更好。
常见 DesignWare 组件类似:
1 | DW01_add |
不过最终写出的 mapped.v 通常还是会变成标准单元,例如:
1 | NAND / NOR / XOR / DFF / AOI / OAI ... |
5. 它们之间的关系
可以这样理解:
1 | RTL 代码 |
五、analyze、elaborate、current_design、link 的作用
脚本片段如下:
1 | analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv" |
这几步是 DC 读取和展开 RTL 的核心流程。
1. analyze
1 | analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv" |
作用:读取并分析 Verilog / SystemVerilog 源码文件。
如果前面有:
1 | set DESIGN_NAME my_top |
那么实际读入的是:
1 | analyze -f sverilog -vcs "./sources/my_top.sv" |
各部分含义:
| 部分 | 作用 |
|---|---|
analyze |
读入 HDL 源码 |
-f sverilog |
指定文件格式是 SystemVerilog |
-vcs |
使用更接近 VCS 的语法兼容方式解析代码 |
"./sources/${DESIGN_NAME}.sv" |
要读入的 RTL 文件路径 |
这一步只是语法分析和读入设计单元,还没有真正把顶层模块展开成完整电路。
可以理解为:
1 | analyze = 读代码 + 检查语法 + 存入 DC 内部库 |
2. elaborate
1 | elaborate ${DESIGN_NAME} |
作用:展开顶层设计。
例如:
1 | elaborate my_top |
DC 会找到 module my_top,然后根据里面的实例化关系,把子模块、参数、generate、always、assign 等内容展开成 DC 内部的通用逻辑结构。
例如 RTL 里有:
1 | module my_top (...); |
elaborate my_top 之后,DC 才会真正建立:
1 | my_top |
简单说:
1 | elaborate = 把顶层设计展开成可综合的内部结构 |
3. current_design
1 | current_design ${DESIGN_NAME} |
作用:指定当前正在操作的设计。
DC 里面可能同时存在很多 design,例如:
1 | my_top |
你需要告诉 DC:
接下来所有约束、检查、综合操作,都是针对
my_top这个顶层。
因此后面的命令:
1 | link |
都会默认作用在当前设计上。
4. link
1 | if {[link] == 0} { |
作用:检查并连接设计中的所有引用。
link 会检查:
- RTL 里实例化的子模块能不能找到
- 引用的库单元能不能找到
- DesignWare 模块能不能找到
- 顶层和子模块之间的连接是否能解析
link_path里设置的库是否足够
例如你的 RTL 中有:
1 | my_fifo u_fifo (...); |
但是你没有读入 my_fifo.v,库里也没有 my_fifo,那么 link 可能会报:
1 | Unable to resolve reference 'my_fifo' |
简单记忆:
1 | link 关注:模块和库单元找不找得到、能不能连起来 |
六、check_design 的作用
脚本中有:
1 | check_design -nosplit > ${RESULTS_DIR}/check_design.precompile.rpt |
1. check_design 检查什么
check_design 的作用是检查当前设计有没有明显的 RTL 或结构问题。
它一般放在:
1 | analyze |
也就是综合前先检查一次设计是否健康。
它会检查类似问题:
- 有没有未连接的 port
- 有没有未驱动的 net
- 有没有多驱动信号
- 有没有组合环路
- 有没有无法解析的 reference
- 有没有不合理的层次结构
- 有没有不完整的设计单元
- 有没有 latch 或不期望的结构提示
例如:
1 | assign y = a; |
同一个 y 被两个地方驱动,check_design 可能会报 multiple drivers。
再例如:
1 | wire temp; |
但是 temp 没有任何地方赋值,可能会报 undriven net。
2. -nosplit 的作用
1 | -nosplit |
作用是:报告输出时不要把长行拆开。
DC 默认有时候会把很长的报告行自动换行,导致阅读和 grep 搜索不方便。
加上 -nosplit 后,报告格式更完整,后续使用下面的命令会更方便:
1 | grep Warning check_design.precompile.rpt |
3. link 和 check_design 的区别
| 命令 | 关注重点 |
|---|---|
link |
模块、库单元、DesignWare 能不能找到并连接上 |
check_design |
当前设计结构本身有没有不合理、不完整、不安全的地方 |
简单说:
1 | link :关注“找不找得到” |
七、两次 check_design 的区别
脚本中有两次 check_design:
1 | check_design -nosplit > ${RESULTS_DIR}/check_design.precompile.rpt |
和:
1 | check_design -nosplit > ${RESULTS_DIR}/check_design.mapped.rpt |
这两次检查的位置不同,作用也不同。
1. 第一次:check_design.precompile.rpt
位置如下:
1 | analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv" |
这时候还没有执行:
1 | create_clock |
因此它检查的是:
RTL 被 elaborate 之后、综合前的设计结构。
主要检查:
- 子模块有没有正确连上
- 有没有未连接端口
- 有没有未驱动信号
- 有没有多驱动信号
- 有没有组合环路
- 有没有不完整的模块引用
- 有没有奇怪的层次结构问题
- 有没有综合前就能发现的结构错误
所以第一次的作用是:
1 | 检查综合前 RTL 展开后的设计有没有问题 |
它更偏向检查 RTL 设计质量。
2. 第二次:check_design.mapped.rpt
位置如下:
1 | compile_ultra |
这时候已经经过了:
1 | compile_ultra |
也就是说,DC 已经把 RTL 逻辑综合成标准单元库里的门级结构了。
例如原来 RTL 中的:
1 | assign y = a & b; |
综合后可能变成:
1 | AND2X1 U1 ( |
第二次 check_design 是在检查这个 mapped gate-level netlist 有没有结构问题。
主要确认:
- 综合后的门级网表是否完整
- 标准单元实例是否都能正确解析
- 有没有综合后产生的未连接网络
- 有没有多驱动网络
- 有没有不合理的端口连接
change_names之后名字是否合法- 最终导出的
mapped.v是否适合作为后端输入
所以第二次的作用是:
1 | 检查综合映射后的门级网表有没有问题 |
它更偏向检查综合结果质量。
3. 核心区别总结
| 检查阶段 | 文件名 | 检查对象 | 重点 |
|---|---|---|---|
| 综合前 | check_design.precompile.rpt |
RTL elaborate 后的设计 | RTL 结构是否健康 |
| 综合后 | check_design.mapped.rpt |
mapped 门级网表 | 综合结果是否健康 |
一句话记忆:
1 | 第一次 check_design:检查 RTL 展开后的设计,防止 RTL 本身有问题。 |
八、常见 LINT 报告解析
在 check_design 或 dc.log 中,可能会看到一些 LINT 类 warning。
1. Unconnected ports (LINT-28)
含义:
有端口没有连接,或者连了但没有被真正使用。
常见原因:
1 | module top( |
这时 in_data[127:0] 这 128 个输入 bit 可能就会报 Unconnected ports。
也可能是例化子模块时端口悬空:
1 | sub_module u_sub ( |
如果 precompile 阶段 Unconnected ports = 128,这个数字很像某个 128 bit 总线没有用到或者没有接上。
例如:
1 | input [127:0] ics_rd_data; |
但 RTL 里暂时没有使用它,就可能出现类似情况。
2. Cells do not drive (LINT-1)
含义:
某个 cell 或模块实例存在,但是它的输出没有驱动任何有效网络。
常见原因:
1 | my_module u1 ( |
或者:
1 | wire unused_y; |
综合前 DC 会认为这部分逻辑是“死逻辑”,也就是算出来了但没人用。
如果是故意保留的调试逻辑,可以暂时不管;如果不是故意的,就要检查是不是输出忘接了。
3. Nets connected to multiple pins on same cell (LINT-33)
含义:
同一个 net 接到了同一个 cell 的多个输入 pin 上。
例如:
1 | assign y = a & a; |
综合后可能相当于:
1 | AND2 U1 ( |
同一个信号 a 同时接到了 AND2 的 A 和 B,DC 就可能报:
1 | Nets connected to multiple pins on same cell |
这种情况很多时候不严重,因为:
1 | a & a |
本质上可以优化成:
1 | a |
但它也可能说明代码写重复了。
例如本来想写:
1 | assign y = a & b; |
结果误写成:
1 | assign y = a & a; |
所以 LINT-33 一般要看具体对象。如果是有意的重复连接,可以不太担心;如果不是有意的,可能是 RTL 写错。
4. Shorted outputs (LINT-31)
含义:
多个输出被直接短接到同一根线上。
这个要重点看。
常见错误:
1 | assign y = a; |
同一个 y 被两个地方驱动。
或者两个模块的输出接到同一个 wire:
1 | wire data; |
u1.out 和 u2.out 都想驱动 data,这就像两个输出同时拉一根线,DC 会认为是输出短接。
还有一种常见情况是端口方向写错。
例如本来某个端口应该是 input,结果写成了 output,然后例化时两个 output 接在一起。
所以:
1 | LINT-31 Shorted outputs 比 LINT-28、LINT-33 更严重,建议优先检查。 |
5. Constant outputs (LINT-52)
含义:
某些输出被优化成常量 0 或常量 1。
例如:
1 | assign out = 1'b0; |
或者:
1 | always @(*) begin |
这就会被认为是 constant output。
也可能是因为某些输入没有连接,被 DC 当成固定值处理,最后经过优化,导致输出变成常量。
例如:
1 | assign out = enable ? data : 1'b0; |
如果 enable 一直被认为是 0,那么 out 就会被优化成 0。
所以 LINT-52 不一定是错。
判断方法:
| 情况 | 处理 |
|---|---|
| 输出本来就应该固定为 0/1 | 可以接受 |
| 期望输出会变化 | 需要检查逻辑是否被错误优化 |
| 由未连接输入导致常量化 | 需要检查端口连接 |
| 某些寄存器位永远不变 | 需要确认是否符合设计预期 |
九、dc.log 信息怎么看
综合完成后,不要只看有没有生成网表,还要分析 dc.log 里的关键信息。
dc.log 会告诉你:
- RTL 解析是否成功
- 参数化模块怎么展开
- 有没有调用 DesignWare
- 综合时做了哪些优化
- 有没有高扇出问题
- 哪些 warning 需要反馈给设计人员
1. Results & Analyze 的整体意思
工具运行结束后,需要关注:
- RTL 是否正常 elaborate
- 参数化模块有没有正确展开
- 工具有没有推断寄存器、存储器、DesignWare 模块
- 有没有 warning
- 有没有逻辑被优化掉
- 有没有高扇出 net
也就是说:
1 | 综合不是跑完就结束了,还要看 log 和 report。 |
2. 参数化模块展开
dc.log 中可能出现类似:
1 | Inferred memory devices in process |
重点是:
1 | my_calc_DATA_WIDTH32_SEL_WIDTH2 |
这说明原来的模块可能是参数化模块,例如:
1 | my_calc #( |
在 elaborate 后,DC 会根据参数值生成一个具体模块名:
1 | my_calc_DATA_WIDTH32_SEL_WIDTH2 |
含义是:
1 | DATA_WIDTH = 32 |
这是很常见的。
因为同一个参数化模块,如果参数不同,综合出来的电路结构也可能不同,所以 DC 会把它们当成不同 design。
如果想单独综合某个参数化模块,可以考虑使用:
1 | elaborate -parameters |
指定参数值。
3. Inferred memory / Register 信息
dc.log 中可能出现类似表格:
1 | Register Name Type Width |
含义是:
DC 在 RTL 中推断出了一个寄存器。
例如 RTL 中有:
1 | always_ff @(posedge clk or negedge rst_n) begin |
DC 会把它识别成触发器,也就是 flip-flop。
表格含义如下:
| 字段 | 含义 |
|---|---|
result4_ff_reg |
推断出来的寄存器名称 |
Flip-flop |
类型是触发器 |
64 |
宽度为 64 bit |
这类信息可以帮你确认:
你期望产生寄存器的地方,DC 是否真的推断出了寄存器。
如果你本来希望是寄存器,但 log 里没看到,可能说明代码没有写成时序逻辑,或者被优化掉了。
4. Netlist for always_ff block is empty (ELAB-984)
可能看到:
1 | Warning: ./sources/my_top.sv:145: Netlist for always_ff block is empty. (ELAB-984) |
含义:
某个
always_ff块没有生成有效网表。
常见原因如下。
情况 1:寄存器赋值没有真正变化
1 | always_ff @(posedge clk) begin |
这个逻辑没有实际意义,DC 可能会认为它是空的。
情况 2:里面的信号后面没人用
1 | always_ff @(posedge clk) begin |
如果 debug_reg 后面完全没有被输出或参与逻辑,综合时可能被优化掉。
情况 3:条件永远不成立
1 | always_ff @(posedge clk) begin |
这种也不会生成有效逻辑。
情况 4:写了空的 always_ff
1 | always_ff @(posedge clk) begin |
这个当然也会报。
这个 warning 不一定是严重错误,但要检查对应行号:
1 | ./sources/my_top.sv:145 |
看看是不是有寄存器被意外优化掉了。
5. DesignWare 是否被调用
dc.log 中可能出现类似:
1 | Loaded alib file './alib-52/svt_ssg_0p72v_m40c.db.alib' |
其中 DW01_NAND2 是 DesignWare 里的模块。
DesignWare,简称 DW,是 Synopsys 提供的一套可综合 IP / 组件库。
如果 RTL 里写了:
1 | assign y = a * b; |
或者写了复杂加法、乘法、比较器,DC 可能会调用 DesignWare 里的优化实现。
看到:
1 | Building model 'DW01_NAND2' |
就说明:
1 | DC 正在构建或调用 DesignWare 相关结构。 |
如果设计里面有乘法器、除法器、复杂算术单元,log 中经常会看到 DW 相关信息。
6. Ungrouping hierarchy
dc.log 中可能出现:
1 | Information: Ungrouping hierarchy u_my_module_0 before Pass 1 |
Ungrouping hierarchy 的意思是:
DC 为了优化,把某些子模块层次打散,合并到上层设计中。
例如原来 RTL 结构是:
1 | my_top |
综合优化时,DC 可能觉得跨层次优化更有利,于是把这些子模块打散,合并到顶层里。
这样做的好处:
- 优化效果更好
- 可能降低面积
- 可能改善时序
坏处是:
- 层次结构不清晰
- 后续 debug 不方便
mapped.v里可能找不到原来的模块层次
如果希望保留层次,可以考虑:
1 | set_ungroup [get_designs my_module] false |
或者对实例设置:
1 | set_dont_touch [get_cells u_my_module] |
不过初学综合时,一般可以先让 DC 自动优化。
7. 寄存器合并
dc.log 中可能出现:
1 | The register 'u_my_calc/a3_synch1_ff_reg[1]' is removed because it is merged to 'u_my_calc/b3_synch1_ff_reg[1]'. |
意思是:
某个寄存器被删除了,因为它和另一个寄存器功能等价,DC 把它们合并了。
例如 RTL 中写了:
1 | always_ff @(posedge clk) begin |
如果 a_reg 和 b_reg 功能完全一样,且没有特殊约束,DC 可能会把两个寄存器合并成一个。
这样可以减少面积。
8. 常量寄存器删除
dc.log 中可能出现:
1 | The register 'result2_ff_reg[46]' is a constant and will be removed. |
意思是:
某些寄存器 bit 永远是常量,因此被 DC 删除。
例如:
1 | always_ff @(posedge clk) begin |
如果这些 bit 永远是 0,DC 就可能不再用触发器实现,而是直接接常量 0。
这类信息说明 DC 正在做:
- 常量传播
- 死逻辑删除
- 等价寄存器合并
- 面积优化
如果这些优化符合预期,那没问题。
如果你本来希望这些寄存器保留下来,就要检查是不是代码逻辑写错,或者需要加:
1 | set_dont_touch |
9. 高扇出网络 HFO
高扇出网络英文是:
1 | High Fanout Net |
简称:
1 | HFO |
dc.log 中可能出现:
1 | Warning: Design 'my_top' contains 1 high-fanout nets. |
意思是:
rst_n这根网络驱动了 1088 个负载,是一个高扇出网络。
这很常见,因为复位信号会连到很多寄存器。
但是高扇出可能带来问题:
- 复位线负载太大
- 延迟变大
- 后端布线压力大
- 可能影响时序
- 可能需要插 buffer tree
在前端综合里,DC 可能只是提示:
fanout 很大,我会用一个默认 fanout number 来估算延迟。
真正处理高扇出,很多时候是在后端 APR 阶段通过 buffer 插入、reset tree、clock/reset network 优化来解决。
10. 哪些高扇出可以忽略,哪些不能忽略
| 高扇出信号 | 是否常见 | 是否需要重点处理 |
|---|---|---|
rst_n |
很常见 | 可以先记录,后端通常会处理 |
clk |
时钟网络 | 应交给 CTS 处理,不应当作普通逻辑 |
enable |
常见控制信号 | 需要关注 |
valid |
常见控制信号 | 需要关注 |
mode |
普通控制信号 | 需要关注 |
state[0] |
状态机控制信号 | 需要关注 |
简单说:
1 | rst_n 的高扇出问题很多时候可以先记录,但普通逻辑信号的高扇出不能随便忽略。 |
如果高扇出来自普通控制信号,可能需要:
- 复制寄存器
- 分层控制
- 减少一个控制信号驱动太多逻辑
- 做 pipeline
- 后端插 buffer
- 通过综合约束限制 fanout
例如可以考虑:
1 | set_max_fanout 32 [current_design] |
或者对特定信号做结构优化。
十、建议使用的 grep 关键词
跑完 DC 后,可以优先搜索这些关键词:
1 | grep -i "Error" dc.log |
1. 最需要重点关注的信息
建议重点看:
ErrorLink ErrorUnresolved referenceNetlist for always_ff block is emptyShorted outputsMultiple drivers- 普通信号的
High-fanout - 大量寄存器被
constant - 大量寄存器被
removed - 设计层次被大量
ungroup
2. 报告文件也建议检查
除了 dc.log,还建议查看这些报告:
1 | report_timing > ${RESULTS_DIR}/timing.rpt |
各报告作用如下:
| 报告 | 作用 |
|---|---|
report_timing |
查看关键路径、setup slack、hold slack |
report_area |
查看面积 |
report_power |
查看功耗 |
report_qor |
查看整体质量,包括 WNS、TNS、面积等 |
report_constraint |
查看约束是否满足 |
check_design |
查看设计结构是否健康 |
十一、总结
这份内容的核心是教你如何看懂一个 DC 综合脚本,以及综合完成后如何分析 dc.log 和 check_design 报告。
1. 脚本流程总结
完整流程可以概括为:
1 | 设置设计名和输出目录 |
2. 关键概念总结
| 概念 | 一句话理解 |
|---|---|
analyze |
读 RTL 源码 |
elaborate |
展开顶层设计 |
current_design |
指定当前操作对象 |
link |
检查模块和库是否能连接 |
check_design |
检查设计结构是否健康 |
target_library |
真实标准单元库 |
synthetic_library |
高级运算综合库 |
link_path 中的 * |
当前 DC 内存中的设计 |
compile_ultra |
执行综合优化 |
change_names |
修正网表命名 |
mapped.v |
最终门级网表 |
3. 两次 check_design 的记忆方法
1 | precompile check_design: |
4. 看 dc.log 的重点
跑完综合后,重点看:
- RTL 是否成功 elaborate
- 参数化模块是否正确展开
- 寄存器和存储器是否正确推断
- DesignWare 是否被调用
- 子模块层次是否被打散
- 哪些寄存器被合并或删除
- 是否有常量寄存器
- 是否有高扇出网络
- 是否有 serious warning
- 是否有 link error 或 unresolved reference
5. 一句话总结
DC 综合不是只要生成
mapped.v就结束了,还要检查约束、报告、log、warning 和最终网表结构是否合理。