设计输入与准备:Verilog 网表解析、Liberty 库文件格式、LEF/DEF 文件介绍、设计数据准备流程
好了,咱们正式开始动手做后端。第一步不是跑脚本,而是搞清楚你手里到底有什么数据。
我见过不少新手,拿到一个设计压缩包就往工具里塞,结果跑两天报一堆错,回头一看——库文件版本不对,或者网表里有个模块根本没例化。嗯,磨刀不误砍柴工,咱们先把这几种输入文件吃透。
Verilog 网表解析:从 RTL 到门级
前端同事会给你一个 .v 文件,这叫门级网表(Gate-Level Netlist)。它不再是 always @(posedge clk) 那种写法,而是直接例化了标准单元——与门、或门、触发器、MUX 这些。
举个例子,一个简单的 D 触发器网表长这样:
module top (clk, rst_n, d, q);
input clk, rst_n, d;
output q;
wire n1;
INV_X1 U1 (.A(d), .ZN(n1));
DFF_X2 U2 (.D(n1), .CK(clk), .RN(rst_n), .Q(q));
endmodule
这里 INV_X1 和 DFF_X2 就是标准单元。工具需要知道它们的时序、面积、功耗信息——这些信息不在网表里,在 Liberty 库里。
grep -i "module" *.v 看看顶层模块名对不对。我曾经有一次因为顶层模块名大小写不一致,浪费了半天查错。
Liberty 库文件格式:标准单元的“身份证”
Liberty 文件(.lib)是后端工具最核心的参考书。它描述了每个标准单元在各种条件下的行为。
一个 Liberty 文件的结构大致如下:
library (typical) {
delay_model : "table_lookup";
voltage_map (VDD, 1.08);
voltage_map (VSS, 0.00);
cell (INV_X1) {
area : 0.56;
pin (A) { direction : input; capacitance : 0.003; }
pin (ZN) {
direction : output;
function : "(!A)";
timing () {
related_pin : "A";
timing_sense : "negative_unate";
cell_rise (delay_template_7x7) {
index_1 ("0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0");
index_2 ("0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0");
values ( \
"0.012, 0.015, 0.020, 0.028, 0.045, 0.080, 0.150", \
...
);
}
}
}
}
}
这里有几个关键点:
- PVT 条件:库名
typical代表典型工艺角。实际项目中会有slow、fast等多个库。 - 时序查找表:
cell_rise是一个 7x7 的二维表,横轴是输入转换时间,纵轴是输出负载电容。工具通过查表和插值得到延迟。 - 引脚电容:每个输入引脚都有固定的电容值,这是驱动强度计算的基础。
voltage_map 必须和你的电源设置一致。我曾经见过有人把 1.08V 的库用在 1.2V 的设计上,时序分析结果完全不准。
LEF/DEF 文件:物理信息的载体
Liberty 管时序,LEF(Library Exchange Format)管物理尺寸和布线规则。DEF(Design Exchange Format)管你的设计实例在芯片上的具体位置。
LEF 文件描述的是标准单元的“外壳”:
MACRO INV_X1
CLASS CORE ;
ORIGIN 0 0 ;
SIZE 0.56 BY 1.4 ;
SYMMETRY X Y ;
PIN A
DIRECTION INPUT ;
PORT
LAYER M1 ; RECT 0.1 0.6 0.2 0.8 ;
END
END A
PIN ZN
DIRECTION OUTPUT ;
PORT
LAYER M1 ; RECT 0.36 0.6 0.46 0.8 ;
END
END ZN
OBS
LAYER M1 ; RECT 0 0 0.56 1.4 ;
END
END INV_X1
你看,LEF 告诉你这个单元占多大面积(0.56 x 1.4),引脚在哪个金属层、什么位置,以及哪些区域不能布线(OBS)。
DEF 文件则是你的设计在芯片上的“快照”:
COMPONENTS 2 ;
- U1 INV_X1 + PLACED ( 10 20 ) N ;
- U2 DFF_X2 + PLACED ( 30 40 ) N ;
END COMPONENTS
NETS 2 ;
- n1 ( U1 ZN ) ( U2 D ) + ROUTED M1 ( 15 25 ) ( 25 25 ) ;
END NETS
DEF 记录了每个单元放在哪里,以及连线怎么走。布局布线工具最终输出的就是 DEF 文件。
| 文件类型 | 扩展名 | 主要内容 | 谁生成 |
|---|---|---|---|
| 门级网表 | .v | 标准单元例化、连接关系 | 前端综合工具 |
| Liberty | .lib | 时序、功耗、面积参数 | 工艺厂 / IP 供应商 |
| LEF | .lef | 单元物理尺寸、引脚位置、布线阻挡 | 工艺厂 / IP 供应商 |
| DEF | .def | 设计实例位置、连线、电源网络 | 后端工具(布局布线后) |
设计数据准备流程
在 OpenROAD 里跑一个设计,数据准备通常分这几步:
- 收集库文件:把工艺厂给的
.lib、.lef放到项目目录。注意区分典型、最坏、最好三个工艺角。 - 检查网表完整性:用
verilator --lint或yosys -q检查有没有悬空引脚、未定义模块。 - 确认顶层模块:在 OpenROAD 里用
read_verilog读入网表,然后用link_design确认所有单元都能找到对应的库。 - 设置时序约束:写一个
.sdc文件,告诉工具时钟周期、输入输出延迟。这一步很多人会忘,但 STA 全靠它。 - 初始化 floorplan:定义芯片尺寸、IO 位置、电源网络。OpenROAD 里用
initialize_floorplan命令。
我的经验:数据准备阶段最容易出问题的就是库文件版本不匹配。比如 Liberty 是 2020 年的,LEF 是 2022 年的,两个文件里单元的引脚定义可能不一样。我建议每次拿到新库,先跑一个 check_library 命令做一致性检查。
下面这张图总结了整个数据流:
小技巧:在 OpenROAD 里,你可以用 read_liberty -lib 同时读入多个工艺角的库。工具会自动根据分析模式选择对应的库。比如 setup 分析用 slow 库,hold 分析用 fast 库。
好了,数据准备这一步走完,你的项目目录应该长这样:
project/
├── rtl/ # 前端 RTL(可选,用于形式验证)
├── netlist/ # 门级网表 (.v)
├── lib/ # Liberty 库 (.lib)
├── lef/ # LEF 文件 (.lef)
├── sdc/ # 时序约束 (.sdc)
├── scripts/ # OpenROAD Tcl 脚本
└── output/ # 输出 DEF、GDS 等
我个人习惯把每个工艺角单独建一个子目录,比如 lib/typical、lib/slow、lib/fast。这样后续切换工艺角时,只需要改脚本里的路径变量,不容易搞混。