简介:《DC基本知识问答》是一份面向数字IC前端工程师、应届生和逻辑综合初学者的速查文档,采用一问一答方式系统介绍Synopsys Design Compiler的核心概念与常用操作。内容覆盖DC在综合流程中的定位,支持的HDL、原理图、网表等输入格式,Verilog、VHDL、EDIF等输出格式,以及转换、逻辑优化、映射三阶段的具体实现;同时整理综合库配置中路径、目标库、链接库的含义,DA图形界面与命令行工作方式的区别,并讲解基于路径的约束、设计对象、起点终点、约束单位和DC脚本等知识点。资源包仅有1个PDF文件,压缩包大小约439KB,体量精简,便于下载后离线收藏和随时查阅。目前已有110人学习,适合笔试面试前或初次使用DC时作为快速入门笔记;通读后能快速建立对综合流程与常用命令的整体认知,减少面对英文手册的茫然感,也可在项目综合阶段遇到概念疑问时按问题定位查阅。
1. DC 综合先搞清楚:这份问答把 Design Compiler 的底给讲透了
综合出来的关键路径 setup 违例堆成山,时钟树插入后 hold 又翻车——问题往往不是代码本身,而是对 DC(Synopsys Design Compiler)的理解还停在表面。这份资源把一线 IC 工程师在实践中反复踩过的 DC 问题整理成了一份问答合集,从工具能吃什么格式、库怎么配,到约束怎么写、报告怎么读,正好覆盖综合流程的主干线。它适合三类人:刚拿到项目要跑综合的初级工程师、准备数字 IC 面试去找 SDC 和综合知识点的人,以及想给现有工程查漏补缺的老手。PDF 是问答式笔记,不像官方文档那么端架子,你遇到问题搜一句话,基本能在里面找到对应回答。
更难得的是它把综合的基础概念和命令绑定在一起讲。比如「基于路径的综合」对应哪四条路径、分别用什么命令约束;「环境配置」对应 search_path 和 target_library 怎么设。这种问答结构天然适合当案头的速查手册,也适合一章一章把 DC 的学习路径走通。下面我按「环境 → 约束 → 流程 → 排错」的顺序拆解它,每一步都会补上命令示例和参数说明。
2. DC 的环境配置:从输入输出格式到 .synopsys_dc.setup
在动手问 DC 要时序报告之前,先把两件事弄清楚:DC 到底吃什么格式的数据,以及你的库配置成什么样。这两件事决定后续所有命令能不能跑通。很多新手跳过这一步直接用 read_verilog,结果 link 阶段报一堆 unresolved reference,反过来才回来配库。
2.1 DC 不只是个综合器:输入输出格式先对齐
DC 的核心任务是把 HDL 描述的电路综合成跟工艺相关的门级电路,并尽可能在 timing、area、power 上同时拿到可接受的结果。它支持多种输入格式,表里列的是最常见的几种:
| 格式 | 说明 |
|---|---|
| .db | Synopsys 专有的二进制库格式,厂商提供的标准单元库、宏单元库基本都是这个 |
| .v | Verilog 源码或门级网表,日常接触最多 |
| .vhd | VHDL 源码或网表 |
| .edif | EDIF 网表格式,跨工具流程做网表交换时常见 |
| .vgh | Verilog 层次化网表的一种呈现形式 |
| .lib | Liberty 时序库文本格式,一般先用 library compiler 转成 .db 再喂给 DC |
输出端除了 .db、.v、.vhd、.edif 之外,DC 还会生成两种下游工具离不开的文件:.sdc 约束文件和 .sdf 标准延迟文件。.sdc 是后端布局布线要用的时序约束,.sdf 是带真实延时的反标文件,交给仿真工具做门级仿真。理解这层关系后你就明白,综合不只是「把 RTL 变成网表」,它其实是整个数字后端流程的数据源头。
2.2 环境配置三件套:search_path、target_library 和 link_library
DC 启动后会去读 .synopsys_dc.setup,这个文件决定了工具能看到哪些库、把设计综合到什么工艺上。配置层级有三处:Synopsys 安装目录、用户主目录、工程目录,后者覆盖前者。所以同一个项目里改库版本,只需要在工程目录维护一份自己的 setup,不用动全局配置。
# .synopsys_dc.setup 工程级配置示例 set search_path [list ./rtl ./lib /home/ic/techlib] set target_library [list saed28nm_typ.db] set link_library [list * saed28nm_typ.db dw_foundation.sldb] set symbol_library [list saed28nm.sdb]参数说明很直接。search_path 是库和设计文件的搜索路径,里面写的路径决定了你后面用不带绝对路径的库名时,DC 能不能找到。target_library 是综合映射阶段真正要用的工艺库,DC 会把 GTECH 网表里的通用门电路映射到这个库的具体单元上,所以它必须是你流片对应 foundry 的 std cell 库。link_library 是链接阶段用的库集合,注意开头那个 *,它表示把当前 DC 内存里已经读入的所有设计视为可链接对象,后面再按顺序搜索目标库和 DesignWare 库。symbol_library 只在 Design Analyzer 图形界面里画原理图用,纯命令行流程留空也不影响综合。
配置完怎么验证?启动 dc_shell 后用 report_lib 查看库信息,里面能看到单元数量、时序单位、电容负载单位,还有可用的线载模型。PDF 里提到参数单位由库决定,这点很关键——同样的 set_load 0.5,在一个库单位是 pf、另一个库单位是 fF 的工艺下,含义差一千倍。我每次换库都会先 report_lib 确认单位再写约束。
2.3 找文档与两种接口:SOLD、man、info 和 dc_shell
PDF 里花了不少问答讲帮助渠道,这部分对新手是真有用的。SOLD 是 Synopsys OnLine Document,终端里执行 sold& 就能打开在线文档集合,基本覆盖 Synopsys 所有工具的说明。命令行下我更喜欢用 man + 命令名或者 info + 命令名,比如 man report_timing,比翻网页快得多。
接口方面,Design Analyzer(DA)是 DC 的图形化前端,调用 DC 做综合,可以看逻辑电路图,但前提是库里有 symbol。它对初学者友好,能看到综合结果长什么样;但不建议全流程依赖图形界面,因为脚本化的 dc_shell 才能做回归、改参数、批量跑几十个模块。Synopsys 的综合接口包括自带 dc_shell 和 dc_shell -tcl_mode,区别是前者用 Synopsys 自定义语法,后者脚本遵循 Tcl 语法。这个差异就是日后踩坑的重灾区,我放到避坑章细说。
3. 基于路径的约束:四种 path 和时钟/IO 约束命令
环境配好,接着进入综合的重头戏:约束。DC 是「基于路径」做综合的,不理解路径就去写约束,等于盲调。这一章把路径模型讲透,再给时钟和 IO 的约束模板。
3.1 四种路径与 start point / end point
DC 时序分析里的 path 不是物理走线,而是数据从起点到终点的时序弧,一共四种:
- input port 到寄存器 D 端;
- 寄存器 clk 端到另一个寄存器 D 端;
- 寄存器 clk 端到 output port;
- input port 到 output port 的纯组合路径。
start point 可以是 input port 或寄存器的 clk pin,end point 是寄存器的 data pin 或 output port。约束的本质就是对这四类路径分别加时序要求,让 DC 在优化时把每个环节的延迟分配均匀。
基于路径的约束有几个对应关系要背牢:寄存器到寄存器之间的路径用 create_clock 约束;input 到寄存器用 set_input_delay;寄存器到 output 用 set_output_delay;input 到 output 的纯组合路径用 set_max_delay / set_min_delay。很多新手只写了时钟约束,IO 约束全缺,综合出的结果在后端接口处大量违例,就是这个映射关系没建立起来。
3.2 时钟约束:从 create_clock 到 jitter、skew、latency
描述一个时钟包含两个要素:频率(period)和相位(waveform)。create_clock 建立一个时钟约束,同时把 DC 对时钟网络的所有认知都挂在这个时钟对象上。
# 时钟约束示例(dc_shell -tcl_mode 语法) create_clock -name clk_100m -period 10 -waveform {0 5} [get_ports clk] # jitter 预算:set_clock_uncertainty -setup set_clock_uncertainty -setup 0.2 [get_clocks clk_100m] # skew 预算:set_clock_uncertainty -hold set_clock_uncertainty -hold 0.05 [get_clocks clk_100m] # source latency:时钟源到时钟定义点的延迟 set_clock_latency -source 1.5 [get_clocks clk_100m] # network latency:时钟定义点到寄存器的延迟 set_clock_latency 0.8 [get_clocks clk_100m]-period 10 表示周期 10ns,也就是 100MHz。-waveform {0 5} 定义上升沿在 0ns、下降沿在 5ns,默认占空比 50%,不同占空比的时钟要显式写 waveform。
set_clock_uncertainty 的 -setup 用来约束时钟抖动 jitter 的预算,-hold 用来约束时钟偏斜 skew 的预算。jitter 是时钟沿在时间轴上的抖动幅度,skew 是同一时钟到达不同触发器的相位差,这两个概念 PDF 里反复强调要区分清楚。实际项目中我习惯把 jitter 和 skew 都放进 uncertainty,分别留 0.2 和 0.05,再根据后端反馈收敛。
latency 分两类:-source 是时钟源到时钟定义点的外部延迟,常用于 PLL 或片外晶振的场景;不加 -source 默认是 network latency,即时钟定义点到触发器时钟端的内部延迟。综合阶段网络 latency 通常用 0 或估一个值,CTS 之后反标真实值。
如果设计里有 PLL,PDF 的建议是先用 create_clock 约束输入参考时钟,再用 create_propagated_clock 约束 PLL 输出时钟;两者 clock path 都要从 leaf cell 出发,这样时钟树综合才能正确传播延迟。还有一种虚拟时钟,它只在当前设计外部的接口处存在,用于描述跨模块的时序关系,比如外部寄存器的时钟。虚拟时钟不绑在物理 pin 上,create_clock -name vclk -period 10 即可,但 PDF 作者也说了实际工作中基本不设置——我认同,非必要不引入虚拟时钟,约束文件会更好维护。
3.3 端口与组合路径:set_input_delay 到 set_multicycle_path
IO 约束描述的是当前设计边界外的时序情况。set_input_delay 定义信号从外部到达 input port 的延迟,set_output_delay 定义外部模块对当前设计输出端口的时序要求。
# IO 约束示例 set_input_delay 2.0 -clock clk_100m [get_ports data_in] set_input_delay 0.5 -clock clk_100m -add_delay -min [get_ports data_in] set_output_delay 2.5 -clock clk_100m [all_outputs] # 纯组合路径约束 set_max_delay 8.0 -from [get_ports rst_n] -to [get_ports flag]set_input_delay 的数值要从外部芯片数据手册上读:外部器件时钟沿到数据有效的最大延迟,就是这里的 max,最小延迟对应 -min。同一个端口要同时约束 max 和 min,必须加 -add_delay,否则后一条会覆盖前一条。这是新手最容易翻车的点,我见过不少人只写了 max,综合出的 hold 全是负值。
set_false_path 用于让某些路径不参与时序检查,典型场景是跨时钟域信号、异步复位、静态配置信号。比如两个异步时钟域之间的同步器,用 set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] 告知工具不要在这条路径上做 setup/hold 检查。set_multicycle_path 用于数据需要在多个时钟周期内稳定的路径:
# 数据在第二个时钟沿才被采样 set_multicycle_path 2 -setup -from [get_pins inst_reg_reg/C] -to [get_pins inst_reg_reg/D] set_multicycle_path 1 -hold -from [get_pins inst_reg_reg/C] -to [get_pins inst_reg_reg/D]注意 -setup 设为 2 时,-hold 要相应减 1,这是固定搭配,否则 check 窗口会错误地扩大。约束加错了想恢复通用约束,用 reset_path 删除指定的 exception。
4. 综合脚本实战:从 read 到 compile 的完整流程
约束写完之后,就要把 RTL 读进来、做综合。这一章解决两个问题:怎么把设计读进 DC,以及综合策略怎么选。
4.1 read 和 analyze + elaborate 选哪个
读设计有两种方式。第一种是 read_verilog / read_vhdl 直接读,快但糙;第二种是 analyze + elaborate 两段式:
# 方式一:直接用 read dc_shell-t> read_verilog ./rtl/uart_top.v dc_shell-t> link dc_shell-t> compile # 方式二:analyze + elaborate dc_shell-t> analyze -format verilog [list ./rtl/uart_top.v ./rtl/uart_rx.v ./rtl/uart_tx.v] dc_shell-t> elaborate uart_top dc_shell-t> link dc_shell-t> compile两者核心区别有三点。第一,read 命令不自动执行 link,这一点 PDF 里特别强调——read 之后必须手动 link,否则当前设计内部子模块的引用关系没有建立,compile 出来的网表会有悬空引脚。第二,read 对语法错误的检查能力弱一些,analyze 会做完整的语法解析。第三,elaborate 支持在读入过程中传参数,比如 generate 参数、宏定义,并且生成的设计中间表示可以被后续多次复用。我的习惯是多文件 RTL 一律用 analyze + elaborate,单文件快速验证用 read_verilog 省事,但 link 一定不能省。
4.2 compile 的三步:translation、logic optimization、mapping
DC 综合过程拆成三步:translation、logic optimization、mapping。translation 对应 read/analyze + elaborate 阶段,把 HDL 描述转换成与工艺无关的 GTECH 网表,GTECH 就是 Netlist of gates 的中间表示,里面是加法器、乘法器、触发器等逻辑门组成的软宏。logic optimization 和 mapping 在 compile 命令里完成。
优化又分三个层次:结构优化把 RTL 转换成 GTECH 结构;逻辑优化在 GTECH 上做 architectural(structure)和 flatten 变换;门级优化把优化后的 GTECH 映射到实际工艺库单元。PDF 里建议综合时同时生成 structural 和 flatten 两种格式的网表对比,这是个好习惯——structural 会尽量保留层次和共享逻辑,flatten 把中间层次打平、面积往往更小但电路结构难读。
综合时不想让某些库单元参与映射,用 set_dont_use:
# 禁用库里的 BUF 类和特定尺寸单元 set_dont_use [get_lib_cells saed28nm_typ/BUF*] set_dont_use [get_lib_cells saed28nm_typ/AND2X2]这招常用于低功耗设计,比如只允许使用高阈值电压单元,把普通阈值单元全部 set_dont_use。注意 set_dont_use 和 set_dont_touch 的差别:前者是禁止库单元参与映射,后者是保护当前设计或网络不被优化改动。比如保护时钟网络不插入 buffer,用 set_dont_touch_network [get_nets clk_net],而不是 set_dont_touch——后者连 logic optimization 都不会碰,容易过头。
compile 命令本身的选项也值得说。常用的 -map_effort medium/high 控制映射努力程度,-area_effort 控制面积优化力度,综合时间不够时先降 map_effort。PDF 第 3.7 条提到的 -incremental 是增量编译:设计已经映射为门后,如果只改了某段时序约束,用 compile -incremental 会尽量维持已有电路结构,只做局部时序改善,不添加非必要逻辑。这个选项在每次 ECO 式综合里几乎必用。
4.3 top-down 还是 bottom-up:两种策略的取舍
综合策略直接影响编译时间和结果质量。top-down 只写一个顶层脚本,把整个设计当整体综合,时序路径跨模块分配得比较均衡,但缺点很致命:任何子模块改动都要重编整个设计,多时钟设计收敛效果也不理想。bottom-up 是每个子模块单独写脚本综合,在顶层再组装,优点是多时钟设计友好、模块改动不牵连全局,缺点是脚本数量多、需要维护模块间的时序预算。
| 对比项 | top-down | bottom-up |
|---|---|---|
| 脚本数量 | 1 个 | 每模块 1 个 |
| 编译时间 | 单次长 | 单次短,可并行 |
| 子模块改动 | 全量重编 | 只需重编该模块 |
| 多时钟设计 | 收敛难 | 每模块独立收敛 |
| 时序分配 | 自动全局优化 | 需手动 time-budget |
time-budget 在 bottom-up 里是绕不开的:给每个子模块分配输入输出延迟预算,常用工具是 Synopsys 的 characteristic 命令,它自动提取模块边界时序并生成约束。多引用子模块的处理也有讲究:一个模块被例化了多次且各实例约束不同,用 uniquify 命令在内存里复制多份子设计逐份综合;若几个实例环境相同,可以综合一次后 set_dont_touch 防止顶层编译时再改动;第三种做法是 flatten 打平直接综合。三种方法 PDF 都列了,实际项目里我优先 uniquify,因为保留层次结构对后端 floorplan 更友好。
综合收尾的脚本我一般这样写:
# 完整综合脚本 top-down 示例 set TOP "uart_top" set RTL_FILES [list ./rtl/uart_top.v ./rtl/uart_rx.v ./rtl/uart_tx.v] analyze -format verilog $RTL_FILES elaborate $TOP link uniquify # 时钟与 IO 约束 create_clock -name clk_50m -period 20 [get_ports clk] set_clock_uncertainty -setup 0.3 [get_clocks clk_50m] set_clock_uncertainty -hold 0.1 [get_clocks clk_50m] set_input_delay 4.0 -clock clk_50m [all_inputs] set_output_delay 4.0 -clock clk_50m [all_outputs] set_max_area 0 compile -map_effort medium -area_effort medium report_timing -path full -delay max -nworst 10 report_area report_constraint -all_violators write -format verilog -hierarchy -output ./out/${TOP}_netlist.v write_sdc ./out/${TOP}.sdccompile 前先 link,compile 后再 report 和 write 网表、SDC。write_sdc 导出的约束文件要交给后端做 floorplan 和 CTS,这里有个习惯值得养成:每轮综合的网表和 sdc 都按版本号目录存放,方便后端同事回溯。
5. DC 避坑指南:五个综合现场常见的踩坑记录
综合脚本报错和结果异常,九成是配置或语法问题。这章记录五个我在实际项目和同事的问题里反复见到的坑,按「现象 → 原因 → 解决」写。
5.1 unresolved reference:link_library 忘加 * 的下场
现象:link 阶段报 warning:Can't find cell 'xxx' in link_library,综合出来的网表顶层端口悬空,仿真直接挂。
原因:link_library 里没写 *,导致 DC 无法链接当前内存里已读入的模块和库单元。PDF 明确要求 link_library 设置时加 * 表示内存中所有库,很多人只写了目标库的 .db 文件名,子模块相互引用解析不了。
解决:set link_library [list * saed28nm_typ.db dw_foundation.sldb],然后检查 search_path 是否包含所有库文件所在目录。改完重启 dc_shell 重新 link,unresolved reference 清零再往下走。
5.2 read 之后直接 compile:时序报告全是理想时钟
现象:用 read_verilog 读入 RTL 后直接 compile,report_timing 里 setup 全绿,下到后端做 CTS 后违例爆炸。
原因:read 命令不自动执行 link。没有 link 的当前设计,子模块的时序弧没有真正建立,DC 会把所有交叉模块路径当成理想传输,时序报告自然乐观得一塌糊涂。这是 PDF 里明确标注的一条,也是新手最容易踩的暗坑。
解决:read 之后必须手动 link。更稳的做法是放弃 read,改用 analyze + elaborate,elaborate 结束后的设计层次完整,再 link 一步到位。从那以后我见到 read_verilog 后面不跟 link 的脚本,都会直接判定为不合格脚本。
5.3 report_cell 只能看顶层:子模块面积去哪了
现象:report_area 的总面积和版图实现面积对不上,想查是哪个子模块占了大头,report_cell 只列出顶层下一级的少数几个 cell,子模块内部一片空白。
原因:report_cell 缺省行为是列出 current_design 下一级的 cell,不会递归展开所有层次。PDF 里给了两条解决思路,缺一不可。
解决:第一种是 report_cell [get_cells -hier *],加 -hier 参数递归查全部层次;第二种是先 list_design 列出所有 design,cd 到目标子模块把 current_design 切换过去,再直接 report_cell。我实际用第一种更顺手,一条命令看全层次。
5.4 shell 语法混用:dc_shell 与 tcl_mode 的命令差异
现象:在 dc_shell -tcl_mode 里执行 set_input_delay 1.0 all_inputs(),报错 invalid command name "all_inputs",脚本在第一行就挂掉。
原因:dc_shell 传统模式把命令和对象写在一起,all_inputs 后跟圆括号;tcl_mode 下必须用方括号将命令括起来作为函数调用,写成 [all_inputs]。两种语法一旦混用,解析器把 all_inputs 当成 Tcl 命令名来找,自然找不到。PDF 在讲查找约束对象时特意对比了这两种写法的差异。
解决:统一脚本运行环境。新项目一律用 dc_shell -tcl_mode,脚本里所有对象全部写成 [get_ports xxx]、[all_inputs] 这种方括号形式。老项目如果保留了传统 dc_shell 脚本,要么转成 tcl 语法,要么启动时明确 dc_shell 不加参数,两种模式不要混搭。
5.5 top-down 编译时间翻倍:子模块改动牵一发动全身
现象:顶层综合跑了一个半小时,中途 RTL 一个小模块改了一个 always 块,重新综合又等一个半小时,一天只能迭代两三轮。
原因:top-down 策略把整个设计当单一实体优化,任何子模块的 RTL 变化都会导致该模块对应的 GTECH 结构变化,DC 为了全局最优会把相关逻辑全部重映射,而不是只改局部。PDF 在讲 top-down 缺点时列了两条:编译时间长、子模块改变则整个设计都要重新综合。
解决:多模块项目改用 bottom-up,每个子模块独立综合、独立加约束,顶层仅做组装和时序验证。子模块改动时只需重编该模块,顶层用 compile -incremental 保持已有结构完成增量更新。如果顶层和子模块是同一拨人维护,给每个模块写独立脚本的成本并不高,换来的迭代速度值得。
6. 报告先看 violators:三条命令把综合结果查干净
综合完第一步不是看面积,也不是翻 timing report 的完整打印,而是先查违例。report_constraint -all_violators 只列出违反约束的路径,干净的报告应该一条输出都没有:
report_constraint -all_violators report_timing -path full -delay max -nworst 20 report_hierarchy第一个命令把设计规则违例和时序违例一起列出来,DRC 违例如 max_capacitance、max_fanout、max_transition 超标,会直接限制单元输入的跳变时间,这些违例不清理,后端 CTS 做完也救不回来。第二个命令看具体时序路径,-delay max 对应 setup,-delay min 对应 hold,-nworst 20 表示一次看最差的 20 条,不用翻完整份报告。-path full 会把 startpoint 到 endpoint 的整条路径展开,定位是哪一级的组合逻辑贡献了大头。第三个命令 report_hierarchy 看层次化的门类型统计,能反推综合策略是否生效——比如某个模块被意外 flatten 了,门类型分布会和预期明显不同。
如果 report_constraint 有违例,我一般按这个顺序排查:先看是不是约束本身的问题,比如 set_input_delay 的时钟名写错、set_false_path 打过劲儿把该检查的路径滤掉了;再看代码结构,是不是存在跨模块的长组合逻辑链;最后才考虑换 compile 策略或调 map_effort。PDF 里提到的 report_design、report_net、report_timing_requirements 也是配套工具,需要细查某条 exception 是否生效时,report_timing_requirements 能看到当前设计里所有 false path 和 multicycle path,这是约束排查最直接的入口。
综合出网表不等于流程结束,我一直保留的习惯是每跑完一轮综合强制走一遍 report_constraint -all_violators,看到 0 violators 才允许自己往下写 write 网表和 sdc。这个方法帮我挡掉了至少三次因为跨时钟域约束漏配而导致的返工。希望帮到你。
本文还有配套的精品资源,点击获取