(2)怎么检查逻辑综合结果

DC 逻辑综合脚本与日志分析整理

来源:用户粘贴文本整理
主题:Design Compiler 逻辑综合脚本、多时钟约束、库文件区别、check_designdc.log 分析
说明:本文档为学习笔记式整理,内容经过重新排版、归纳和补充,便于复习与查阅。


目录


一、示例综合脚本

下面是一个较完整的 DC 逻辑综合脚本示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#------------------------------
#set initials

set DESIGN_NAME my_top
set TAG ${DESIGN_NAME}_syn_basicrun
set CORE_NUM 16

set_host_options -max_cores $CORE_NUM
define_design_lib WORK -path ./WORK

set RESULTS_DIR ./results/${TAG}
if {![file exists $RESULTS_DIR]} {
file mkdir $RESULTS_DIR
}

#-----------------------------
#set library path

set_app_var search_path "$search_path ../../library/std/NLDM"
set_app_var target_library "svt_ssg_0p72v_m40c.db"
set_app_var synthetic_library "standard.sldb dw_foundation.sldb"
set_app_var link_path "* $target_library $synthetic_library"

#-----------------------------
#elaborate and analyze

analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv"
elaborate ${DESIGN_NAME}
current_design ${DESIGN_NAME}

if {[link] == 0} {
echo "\[USR-INFO\] Link Error!"
}

#----------------------------
#pre compile

check_design -nosplit > ${RESULTS_DIR}/check_design.precompile.rpt

write_file -hierarchy -format ddc -output ${RESULTS_DIR}/${DESIGN_NAME}.precompile.ddc
write_file -hierarchy -format verilog -output ${RESULTS_DIR}/${DESIGN_NAME}.precompile.v

#-----------------------------
#add constraint

create_clock -period 1.0 [get_ports clk]
create_clock -period 0.8 [get_ports clk1]
create_clock -period 0.8 [get_ports clk2]
create_clock -period 0.8 [get_ports clk3]
create_clock -period 0.8 [get_ports clk4]

#-----------------------------
#compile

compile_ultra

#-----------------------------
#post compile

write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.postcompile.v
write_file -format ddc -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.postcompile.ddc

change_names -rules verilog -hierarchy

write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.mapped.v
write_file -format ddc -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.mapped.ddc

check_design -nosplit > ${RESULTS_DIR}/check_design.mapped.rpt

#exit

二、多时钟注意点

脚本中创建了 5 个时钟:

1
2
3
4
5
create_clock -period 1.0 [get_ports clk]
create_clock -period 0.8 [get_ports clk1]
create_clock -period 0.8 [get_ports clk2]
create_clock -period 0.8 [get_ports clk3]
create_clock -period 0.8 [get_ports clk4]

这里需要注意:多时钟设计不能只创建时钟,还要说明时钟之间的关系。


1. 如果这些时钟是异步时钟

如果 clkclk1clk2clk3clk4 之间完全独立,没有固定相位关系,也没有固定频率关系,那么应该加:

1
2
3
4
5
6
set_clock_groups -asynchronous \
-group [get_clocks clk] \
-group [get_clocks clk1] \
-group [get_clocks clk2] \
-group [get_clocks clk3] \
-group [get_clocks clk4]

这样可以告诉 DC:

这些时钟之间是异步关系,不要分析它们之间的跨时钟路径。

否则 DC 可能会默认这些时钟之间存在时序关系,从而分析一些本来不应该分析的路径。


2. 如果 clk1 ~ clk4 是由 clk 分频或倍频得到的

如果 clk1clk2clk3clk4 是由 clk 通过 PLL、分频器、门控或其他逻辑产生的,就不能简单写成异步时钟。

这时应该使用:

1
create_generated_clock

例如:

1
2
3
4
5
create_generated_clock \
-name clk_div2 \
-source [get_ports clk] \
-divide_by 2 \
[get_pins u_div/clk_out]

简单记忆:

时钟关系 推荐约束
完全独立、无固定关系 set_clock_groups -asynchronous
由主时钟分频 / 倍频产生 create_generated_clock
同源但不同相位 根据实际相位关系建模
跨时钟有 CDC 同步器 需要结合 CDC 设计和 false path / async group 处理

脚本中有:

1
set_app_var link_path "* $target_library $synthetic_library"

这里的 * 表示:

DC 当前内存中已经读入的设计。

也就是说,* 可以让 DC 在 link 的时候,不仅去库里面找 cell,也会在当前已经读入的 RTL 设计中寻找模块。


1. 为什么需要 *

例如你的顶层模块中例化了一个子模块:

1
2
3
module my_top (...);
my_fifo u_fifo (...);
endmodule

如果 my_fifo 这个模块已经通过 analyze 读入 DC 内存,但是 link_path 里没有 *,DC 在链接时可能找不到它。

所以:

1
set_app_var link_path "* $target_library $synthetic_library"

可以理解为:

链接时,既要查找当前设计,也要查找标准单元库和综合库。


2. 简单记忆

1
2
3
*                  :当前 DC 内存中的设计
target_library :真实标准单元库
synthetic_library :综合用的高级运算库

四、target_librarysynthetic_library 的区别

脚本中有:

1
2
set_app_var target_library "svt_ssg_0p72v_m40c.db"
set_app_var synthetic_library "standard.sldb dw_foundation.sldb"

这两类库的作用不同。


1. 总体区别

库变量 文件类型 主要作用 最终网表里会不会出现
target_library .db 真实标准单元库,综合最终映射到这里面的 cell
synthetic_library .sldb 综合过程中的高级运算 / 虚拟库,用于优化加法器、乘法器、比较器等结构 一般不会直接出现

2. target_library:真实工艺库

1
set_app_var target_library "svt_ssg_0p72v_m40c.db"

这个 .db 文件是标准单元库,里面包含真实工艺下可用的 cell,例如:

  • AND2
  • OR2
  • NAND2
  • NOR2
  • INV
  • DFF
  • MUX
  • AOI
  • OAI
  • BUF

DC 综合后,最终会把 RTL 逻辑映射成这个库里的标准单元。

例如 RTL 中写:

1
assign y = a & b;

综合后可能变成类似:

1
2
3
4
5
AND2_X1 U1 (
.A(a),
.B(b),
.Y(y)
);

这里的 AND2_X1 就来自 target_library


3. synthetic_library:综合用的高级库

1
set_app_var synthetic_library "standard.sldb dw_foundation.sldb"

synthetic_library 不是具体工艺的标准单元库,而是 DC 用来识别和优化高级运算结构的库。

例如 RTL 中写:

1
2
assign sum     = a + b;
assign product = a * b;

DC 不一定一开始就直接用一堆 ANDORXOR 去硬拼,而是可能先识别为:

  • 加法器
  • 乘法器
  • 比较器
  • 移位器
  • 除法器

这些高级结构可能先映射到 synthetic_library 里面的组件,然后再进一步综合成 target_library 里的真实标准单元。


4. standard.sldbdw_foundation.sldb

作用
standard.sldb DC 自带的基础 synthetic library
dw_foundation.sldb DesignWare 基础库,提供更丰富的加法器、乘法器、比较器、除法器等高级算术模块

例如:

1
assign y = a * b;

如果有 dw_foundation.sldb,DC 可能会调用 DesignWare 里的乘法器结构,优化效果通常更好。

常见 DesignWare 组件类似:

1
2
3
4
DW01_add
DW02_mult
DW_cmp
DW_shifter

不过最终写出的 mapped.v 通常还是会变成标准单元,例如:

1
NAND / NOR / XOR / DFF / AOI / OAI ...

5. 它们之间的关系

可以这样理解:

1
2
3
4
5
6
7
8
9
RTL 代码

DC 识别高级运算结构

synthetic_library 帮助优化加法器、乘法器等结构

target_library 映射成真实标准单元

门级网表 netlist

脚本片段如下:

1
2
3
4
5
6
7
analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv"
elaborate ${DESIGN_NAME}
current_design ${DESIGN_NAME}

if {[link] == 0} {
echo "\[USR-INFO\] Link Error!"
}

这几步是 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,然后根据里面的实例化关系,把子模块、参数、generatealwaysassign 等内容展开成 DC 内部的通用逻辑结构。

例如 RTL 里有:

1
2
3
4
module my_top (...);
sub_module u1 (...);
sub_module u2 (...);
endmodule

elaborate my_top 之后,DC 才会真正建立:

1
2
3
my_top
├── u1
└── u2

简单说:

1
elaborate = 把顶层设计展开成可综合的内部结构

3. current_design

1
current_design ${DESIGN_NAME}

作用:指定当前正在操作的设计。

DC 里面可能同时存在很多 design,例如:

1
2
3
4
my_top
sub_module
fifo
alu

你需要告诉 DC:

接下来所有约束、检查、综合操作,都是针对 my_top 这个顶层。

因此后面的命令:

1
2
3
4
5
link
check_design
compile_ultra
report_timing
report_area

都会默认作用在当前设计上。


1
2
3
if {[link] == 0} {
echo "\[USR-INFO\] Link Error!"
}

作用:检查并连接设计中的所有引用。

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
2
3
4
5
6
7
8
9
10
11
analyze

elaborate

current_design

link

check_design

compile_ultra

也就是综合前先检查一次设计是否健康。

它会检查类似问题:

  • 有没有未连接的 port
  • 有没有未驱动的 net
  • 有没有多驱动信号
  • 有没有组合环路
  • 有没有无法解析的 reference
  • 有没有不合理的层次结构
  • 有没有不完整的设计单元
  • 有没有 latch 或不期望的结构提示

例如:

1
2
assign y = a;
assign y = b;

同一个 y 被两个地方驱动,check_design 可能会报 multiple drivers。

再例如:

1
2
wire temp;
assign y = temp;

但是 temp 没有任何地方赋值,可能会报 undriven net。


2. -nosplit 的作用

1
-nosplit

作用是:报告输出时不要把长行拆开。

DC 默认有时候会把很长的报告行自动换行,导致阅读和 grep 搜索不方便。

加上 -nosplit 后,报告格式更完整,后续使用下面的命令会更方便:

1
2
grep Warning check_design.precompile.rpt
grep Error check_design.precompile.rpt

命令 关注重点
link 模块、库单元、DesignWare 能不能找到并连接上
check_design 当前设计结构本身有没有不合理、不完整、不安全的地方

简单说:

1
2
link         :关注“找不找得到”
check_design : 关注“设计本身健不健康”

七、两次 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
2
3
4
5
6
analyze -f sverilog -vcs "./sources/${DESIGN_NAME}.sv"
elaborate ${DESIGN_NAME}
current_design ${DESIGN_NAME}
link

check_design -nosplit > ${RESULTS_DIR}/check_design.precompile.rpt

这时候还没有执行:

1
2
create_clock
compile_ultra

因此它检查的是:

RTL 被 elaborate 之后、综合前的设计结构。

主要检查:

  1. 子模块有没有正确连上
  2. 有没有未连接端口
  3. 有没有未驱动信号
  4. 有没有多驱动信号
  5. 有没有组合环路
  6. 有没有不完整的模块引用
  7. 有没有奇怪的层次结构问题
  8. 有没有综合前就能发现的结构错误

所以第一次的作用是:

1
检查综合前 RTL 展开后的设计有没有问题

它更偏向检查 RTL 设计质量。


2. 第二次:check_design.mapped.rpt

位置如下:

1
2
3
4
5
6
7
8
9
10
11
compile_ultra

write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.postcompile.v
write_file -format ddc -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.postcompile.ddc

change_names -rules verilog -hierarchy

write_file -format verilog -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.mapped.v
write_file -format ddc -hierarchy -output ${RESULTS_DIR}/${DESIGN_NAME}.mapped.ddc

check_design -nosplit > ${RESULTS_DIR}/check_design.mapped.rpt

这时候已经经过了:

1
compile_ultra

也就是说,DC 已经把 RTL 逻辑综合成标准单元库里的门级结构了。

例如原来 RTL 中的:

1
assign y = a & b;

综合后可能变成:

1
2
3
4
5
AND2X1 U1 (
.A(a),
.B(b),
.Y(y)
);

第二次 check_design 是在检查这个 mapped gate-level netlist 有没有结构问题。

主要确认:

  1. 综合后的门级网表是否完整
  2. 标准单元实例是否都能正确解析
  3. 有没有综合后产生的未连接网络
  4. 有没有多驱动网络
  5. 有没有不合理的端口连接
  6. change_names 之后名字是否合法
  7. 最终导出的 mapped.v 是否适合作为后端输入

所以第二次的作用是:

1
检查综合映射后的门级网表有没有问题

它更偏向检查综合结果质量。


3. 核心区别总结

检查阶段 文件名 检查对象 重点
综合前 check_design.precompile.rpt RTL elaborate 后的设计 RTL 结构是否健康
综合后 check_design.mapped.rpt mapped 门级网表 综合结果是否健康

一句话记忆:

1
2
第一次 check_design:检查 RTL 展开后的设计,防止 RTL 本身有问题。
第二次 check_design:检查综合映射后的网表,确认最终 mapped netlist 没问题。

八、常见 LINT 报告解析

check_designdc.log 中,可能会看到一些 LINT 类 warning。


1. Unconnected ports (LINT-28)

含义:

有端口没有连接,或者连了但没有被真正使用。

常见原因:

1
2
3
4
5
6
7
8
9
module top(
input [127:0] in_data,
output out
);

assign out = 1'b1;

// in_data 完全没用到
endmodule

这时 in_data[127:0] 这 128 个输入 bit 可能就会报 Unconnected ports

也可能是例化子模块时端口悬空:

1
2
3
4
5
6
sub_module u_sub (
.clk(clk),
.rst_n(rst_n),
.data_in(), // 没接
.data_out(out)
);

如果 precompile 阶段 Unconnected ports = 128,这个数字很像某个 128 bit 总线没有用到或者没有接上。

例如:

1
input [127:0] ics_rd_data;

但 RTL 里暂时没有使用它,就可能出现类似情况。


2. Cells do not drive (LINT-1)

含义:

某个 cell 或模块实例存在,但是它的输出没有驱动任何有效网络。

常见原因:

1
2
3
4
5
my_module u1 (
.a(a),
.b(b),
.y() // 输出没接
);

或者:

1
2
3
4
5
wire unused_y;

assign unused_y = a & b;

// unused_y 后面完全没用

综合前 DC 会认为这部分逻辑是“死逻辑”,也就是算出来了但没人用。

如果是故意保留的调试逻辑,可以暂时不管;如果不是故意的,就要检查是不是输出忘接了。


3. Nets connected to multiple pins on same cell (LINT-33)

含义:

同一个 net 接到了同一个 cell 的多个输入 pin 上。

例如:

1
assign y = a & a;

综合后可能相当于:

1
2
3
4
5
AND2 U1 (
.A(a),
.B(a),
.Y(y)
);

同一个信号 a 同时接到了 AND2AB,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
2
assign y = a;
assign y = b;

同一个 y 被两个地方驱动。

或者两个模块的输出接到同一个 wire:

1
2
3
4
5
6
7
8
9
wire data;

mod1 u1 (
.out(data)
);

mod2 u2 (
.out(data)
);

u1.outu2.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
2
3
always @(*) begin
out = 1'b0;
end

这就会被认为是 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 的整体意思

工具运行结束后,需要关注:

  1. RTL 是否正常 elaborate
  2. 参数化模块有没有正确展开
  3. 工具有没有推断寄存器、存储器、DesignWare 模块
  4. 有没有 warning
  5. 有没有逻辑被优化掉
  6. 有没有高扇出 net

也就是说:

1
综合不是跑完就结束了,还要看 log 和 report。

2. 参数化模块展开

dc.log 中可能出现类似:

1
2
Inferred memory devices in process
in routine my_calc_DATA_WIDTH32_SEL_WIDTH2 line 159

重点是:

1
my_calc_DATA_WIDTH32_SEL_WIDTH2

这说明原来的模块可能是参数化模块,例如:

1
2
3
4
my_calc #(
.DATA_WIDTH(DATA_WIDTH),
.SEL_WIDTH(SEL_WIDTH)
) u_my_calc (...);

在 elaborate 后,DC 会根据参数值生成一个具体模块名:

1
my_calc_DATA_WIDTH32_SEL_WIDTH2

含义是:

1
2
DATA_WIDTH = 32
SEL_WIDTH = 2

这是很常见的。

因为同一个参数化模块,如果参数不同,综合出来的电路结构也可能不同,所以 DC 会把它们当成不同 design。

如果想单独综合某个参数化模块,可以考虑使用:

1
elaborate -parameters

指定参数值。


3. Inferred memory / Register 信息

dc.log 中可能出现类似表格:

1
2
Register Name    Type        Width
result4_ff_reg Flip-flop 64

含义是:

DC 在 RTL 中推断出了一个寄存器。

例如 RTL 中有:

1
2
3
4
5
6
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
result4_ff <= '0;
else
result4_ff <= result4_next;
end

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
2
3
always_ff @(posedge clk) begin
a <= a;
end

这个逻辑没有实际意义,DC 可能会认为它是空的。

情况 2:里面的信号后面没人用

1
2
3
always_ff @(posedge clk) begin
debug_reg <= data;
end

如果 debug_reg 后面完全没有被输出或参与逻辑,综合时可能被优化掉。

情况 3:条件永远不成立

1
2
3
4
always_ff @(posedge clk) begin
if (1'b0)
a <= b;
end

这种也不会生成有效逻辑。

情况 4:写了空的 always_ff

1
2
always_ff @(posedge clk) begin
end

这个当然也会报。

这个 warning 不一定是严重错误,但要检查对应行号:

1
./sources/my_top.sv:145

看看是不是有寄存器被意外优化掉了。


5. DesignWare 是否被调用

dc.log 中可能出现类似:

1
2
Loaded alib file './alib-52/svt_ssg_0p72v_m40c.db.alib'
Building model 'DW01_NAND2'

其中 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
2
3
4
Information: Ungrouping hierarchy u_my_module_0 before Pass 1
Information: Ungrouping hierarchy u_my_module_1 before Pass 1
Information: Ungrouping hierarchy u_my_module_2 before Pass 1
Information: Ungrouping hierarchy u_my_module_3 before Pass 1

Ungrouping hierarchy 的意思是:

DC 为了优化,把某些子模块层次打散,合并到上层设计中。

例如原来 RTL 结构是:

1
2
3
4
5
my_top
├── u_my_module_0
├── u_my_module_1
├── u_my_module_2
└── u_my_module_3

综合优化时,DC 可能觉得跨层次优化更有利,于是把这些子模块打散,合并到顶层里。

这样做的好处:

  1. 优化效果更好
  2. 可能降低面积
  3. 可能改善时序

坏处是:

  1. 层次结构不清晰
  2. 后续 debug 不方便
  3. 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
2
3
4
always_ff @(posedge clk) begin
a_reg <= din;
b_reg <= din;
end

如果 a_regb_reg 功能完全一样,且没有特殊约束,DC 可能会把两个寄存器合并成一个。

这样可以减少面积。


8. 常量寄存器删除

dc.log 中可能出现:

1
2
The register 'result2_ff_reg[46]' is a constant and will be removed.
The register 'result2_ff_reg[47]' is a constant and will be removed.

意思是:

某些寄存器 bit 永远是常量,因此被 DC 删除。

例如:

1
2
3
always_ff @(posedge clk) begin
result2_ff[49:46] <= 4'b0000;
end

如果这些 bit 永远是 0,DC 就可能不再用触发器实现,而是直接接常量 0

这类信息说明 DC 正在做:

  1. 常量传播
  2. 死逻辑删除
  3. 等价寄存器合并
  4. 面积优化

如果这些优化符合预期,那没问题。

如果你本来希望这些寄存器保留下来,就要检查是不是代码逻辑写错,或者需要加:

1
set_dont_touch

9. 高扇出网络 HFO

高扇出网络英文是:

1
High Fanout Net

简称:

1
HFO

dc.log 中可能出现:

1
2
Warning: Design 'my_top' contains 1 high-fanout nets.
Net 'rst_n': 1088 load(s), 1 driver(s)

意思是:

rst_n 这根网络驱动了 1088 个负载,是一个高扇出网络。

这很常见,因为复位信号会连到很多寄存器。

但是高扇出可能带来问题:

  1. 复位线负载太大
  2. 延迟变大
  3. 后端布线压力大
  4. 可能影响时序
  5. 可能需要插 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
2
3
4
5
6
7
8
grep -i "Error" dc.log
grep -i "Warning" dc.log
grep -i "Inferred" dc.log
grep -i "DW" dc.log
grep -i "Ungroup" dc.log
grep -i "removed" dc.log
grep -i "constant" dc.log
grep -i "high-fanout" dc.log

1. 最需要重点关注的信息

建议重点看:

  • Error
  • Link Error
  • Unresolved reference
  • Netlist for always_ff block is empty
  • Shorted outputs
  • Multiple drivers
  • 普通信号的 High-fanout
  • 大量寄存器被 constant
  • 大量寄存器被 removed
  • 设计层次被大量 ungroup

2. 报告文件也建议检查

除了 dc.log,还建议查看这些报告:

1
2
3
4
5
report_timing     > ${RESULTS_DIR}/timing.rpt
report_area > ${RESULTS_DIR}/area.rpt
report_power > ${RESULTS_DIR}/power.rpt
report_qor > ${RESULTS_DIR}/qor.rpt
report_constraint > ${RESULTS_DIR}/constraint.rpt

各报告作用如下:

报告 作用
report_timing 查看关键路径、setup slack、hold slack
report_area 查看面积
report_power 查看功耗
report_qor 查看整体质量,包括 WNS、TNS、面积等
report_constraint 查看约束是否满足
check_design 查看设计结构是否健康

十一、总结

这份内容的核心是教你如何看懂一个 DC 综合脚本,以及综合完成后如何分析 dc.logcheck_design 报告。


1. 脚本流程总结

完整流程可以概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
设置设计名和输出目录

设置 search_path

设置 target_library / synthetic_library / link_path

analyze 读取 RTL

elaborate 展开顶层

current_design 指定当前设计

link 链接子模块和库

check_design 综合前检查

create_clock 添加时钟约束

compile_ultra 综合优化

输出 postcompile 文件

change_names 修正命名

输出 mapped.v / mapped.ddc

check_design 综合后检查

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
2
3
4
5
precompile check_design:
检查 RTL 展开后的设计有没有问题。

mapped check_design:
检查综合后的门级网表有没有问题。

4. 看 dc.log 的重点

跑完综合后,重点看:

  1. RTL 是否成功 elaborate
  2. 参数化模块是否正确展开
  3. 寄存器和存储器是否正确推断
  4. DesignWare 是否被调用
  5. 子模块层次是否被打散
  6. 哪些寄存器被合并或删除
  7. 是否有常量寄存器
  8. 是否有高扇出网络
  9. 是否有 serious warning
  10. 是否有 link error 或 unresolved reference

5. 一句话总结

DC 综合不是只要生成 mapped.v 就结束了,还要检查约束、报告、log、warning 和最终网表结构是否合理。

ESC 关闭 | 导航 | Enter 打开
输入关键词开始搜索