3. 设计输入与准备:RTL 文件组织、Liberty/LEF/DEF 文件格式说明、SDC 约束文件准备、设计目录结构规范

好了,咱们正式开始动手前的最后一步——把“食材”备齐。做芯片后端设计,说白了就是一场“输入-处理-输出”的游戏。输入文件的质量,直接决定了你后面几个星期甚至几个月是顺风顺水,还是天天加班改脚本。

我个人习惯,在跑任何一条 OpenROAD 流程之前,都会花半天时间把输入文件理一遍。别嫌麻烦,这步省下来的时间,后面会加倍还给你。

核心输入清单: RTL(硬件描述)、Liberty(时序库)、LEF(物理库)、DEF(初始布局/布线)、SDC(时序约束)。缺一不可。

3.1 RTL 文件组织:别让代码“跑”丢了

RTL 是设计的源头。很多新手喜欢把所有 .v 文件扔到一个目录里,结果到了综合阶段,读文件顺序错了,或者漏了一个模块,整个流程就崩了。

我建议的目录结构:

rtl/
├── src/                # 设计源文件
│   ├── core/           # 核心逻辑模块
│   ├── peri/           # 外设接口模块
│   └── top/            # 顶层模块
├── tb/                 # 测试激励(后端一般不用,但保留)
├── filelist.f          # 文件列表(核心!)
└── include/            # 头文件/宏定义

为什么要用 filelist.f?因为 OpenROAD 的 read_verilog 命令支持直接读文件列表。你只需要维护一个文本文件,按依赖顺序把 .v 文件列好就行。

filelist.f 示例:

# 核心模块
./src/core/alu.v
./src/core/regfile.v
./src/core/ctrl.v

# 外设模块
./src/peri/uart.v
./src/peri/spi.v

# 顶层
./src/top/chip_top.v
小技巧: 我习惯在 filelist 里用相对路径,这样整个设计目录可以随意移动,不会因为路径报错。另外,注释行用 # 开头,OpenROAD 会自动忽略。

3.2 Liberty 文件:芯片的“速度与功耗”说明书

Liberty(.lib)文件,说白了就是标准单元库的“数据手册”。它告诉工具:每个门有多快、功耗多大、输入电容是多少。

一个典型的 .lib 文件包含:

字段 含义 我的经验
cell 单元名称,如 AND2X1 命名规则一定要统一,不然后面查库很痛苦
pin 引脚定义,包括方向、电容 注意检查输出引脚的 max_transition 限制
timing 时序弧,包括 rise/fall 延迟 不同 PVT 条件下的表格要完整,缺了会报错
power 内部功耗和泄漏功耗 泄漏功耗在先进工艺下越来越重要

在 OpenROAD 里,读 Liberty 的命令很简单:

read_liberty -max /path/to/slow.lib
read_liberty -min /path/to/fast.lib
注意: 一定要同时读入 max(最差工艺角)和 min(最佳工艺角)两个库。工具会同时用这两个库做 setup 和 hold 检查。我曾经只读了一个库,结果 hold 违例完全没报出来,流片回来直接废了。

3.3 LEF 文件:芯片的“物理蓝图”

LEF(Library Exchange Format)描述的是单元的物理尺寸、引脚位置、布线阻挡层等信息。它和 Liberty 是“双胞胎”——一个管时序功耗,一个管物理形状。

LEF 文件的核心内容:

  • MACRO:定义每个标准单元或宏模块
  • SIZE:单元的宽和高(单位通常是微米或纳米)
  • PIN:引脚名称、层、坐标
  • OBS:布线阻挡层,告诉工具哪些区域不能走线

OpenROAD 读 LEF 的命令:

read_lef /path/to/tech.lef    # 工艺LEF(包含金属层定义、间距规则)
read_lef /path/to/cells.lef   # 单元LEF(包含所有标准单元和IO)
避坑指南: 我曾经遇到过一个问题——读入 LEF 后,工具报“unknown layer”。查了半天,发现是 tech.lef 和 cells.lef 的金属层命名不一致。一个叫 M1,另一个叫 METAL1。嗯,从此以后我每次都会先 grep 一下两个文件的 LAYER 定义。

3.4 DEF 文件:设计的“初始状态”

DEF(Design Exchange Format)描述的是整个芯片的物理信息——芯片大小、IO 位置、电源网络、以及初始的单元摆放和布线。

DEF 的主要用途:

  • 初始 DEF:从顶层规划工具导出,包含芯片尺寸和 IO 位置
  • 中间 DEF:在布局、时钟树综合、布线等步骤之间传递设计状态
  • 最终 DEF:输出给物理验证和签核工具

在 OpenROAD 流程中,你通常会在读入 RTL 和库之后,再读入一个初始 DEF:

read_def /path/to/floorplan.def

这个 DEF 里一般只包含芯片边界和 IO 引脚的位置。后面的布局布线结果,OpenROAD 会自己写到新的 DEF 文件里。

3.5 SDC 约束文件:给芯片定“规矩”

SDC(Synopsys Design Constraints)是时序约束的核心。没有它,工具就不知道你的芯片要跑多快、哪些路径是假的、哪些时钟是异步的。

一个典型的 SDC 文件包含:

命令 作用 我常用的写法
create_clock 定义时钟 create_clock -name clk -period 10 [get_ports clk]
set_input_delay 输入路径约束 set_input_delay -clock clk -max 5 [all_inputs]
set_output_delay 输出路径约束 set_output_delay -clock clk -max 5 [all_outputs]
set_false_path 忽略某些路径 set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
警告: SDC 里的时钟定义一定要和 RTL 里的时钟端口名字完全一致。大小写、下划线都不能错。我见过有人把 clk 写成 CLK,结果工具报“clock not found”,查了半天才发现是大小写问题。

3.6 设计目录结构规范:让项目“井井有条”

最后,我分享一下我个人用了多年的目录结构。不管项目大小,这套结构都能让你快速找到需要的文件。

project/
├── rtl/                  # RTL 源文件
│   ├── src/
│   └── filelist.f
├── lib/                  # 库文件
│   ├── liberty/          # .lib 文件
│   ├── lef/              # .lef 文件
│   └── db/               # 工具生成的数据库(可选)
├── constraints/          # SDC 约束
│   └── chip.sdc
├── scripts/              # OpenROAD Tcl 脚本
│   ├── 1_synth.tcl
│   ├── 2_floorplan.tcl
│   ├── 3_place.tcl
│   ├── 4_cts.tcl
│   ├── 5_route.tcl
│   └── 6_finish.tcl
├── output/               # 输出文件
│   ├── synth/
│   ├── floorplan/
│   ├── place/
│   ├── cts/
│   ├── route/
│   └── gds/
└── logs/                 # 日志文件
    └── run.log

这套结构的好处是:每个阶段的输入输出都放在独立的子目录里,不会互相覆盖。而且日志单独放,方便回溯问题。

核心原则: 输入文件只读不写,输出文件按阶段隔离,脚本文件版本控制。做到这三点,你的项目就不会乱。

3.7 本章知识体系总览

下面这张图,把本章讲到的所有输入文件、它们之间的关系、以及它们在 OpenROAD 流程中的位置,做了一个完整的梳理。你可以把它当作一个“快速导航图”。

设计输入与准备 - 知识体系总览 设计输入与准备 - 知识体系总览 设计输入 RTL 文件 Liberty 文件 LEF 文件 DEF 文件 SDC 约束 硬件描述 filelist.f 组织 模块化目录 时序/功耗库 max + min 双库 PVT 角完整 物理库 tech.lef + cells.lef 引脚/阻挡层 设计交换格式 芯片尺寸/IO 阶段状态传递 时序约束 时钟/IO延迟 false_path OpenROAD 流程阶段 综合 布局规划 布局 时钟树 布线 签核输出 💡 核心原则:输入文件只读不写,输出文件按阶段隔离,脚本文件版本控制 每个阶段都会读入上一阶段的 DEF,并写出新的 DEF 给下一阶段

这张图把整个流程串起来了。你从左边准备好 RTL、Liberty、LEF、DEF 和 SDC,然后 OpenROAD 会依次经过综合、布局规划、布局、时钟树、布线,最后输出签核结果。每个阶段都会读入上一阶段的 DEF,并写出新的 DEF 给下一阶段。

嗯,到这里,设计输入与准备的内容就讲完了。把这些文件理清楚,你的 OpenROAD 流程就已经成功了一半。