3. LEF/DEF设计数据读入与合法性检查
任务说明
本案例编写一个 Python 脚本,读入 OpenROAD 流程所需的 LEF(工艺/单元库)与 DEF(设计)文件,检查三类关键定义是否完整:
- 单元(MACRO):DEF 中实例化的每个单元,是否都能在 LEF 中找到对应 MACRO 定义。
- 引脚(PIN):DEF 中每个实例的每个连接引脚,是否都能在对应 MACRO 的 PIN 定义中找到。
- 布线层(LAYER):DEF 中布线/走线引用的层,是否都在 LEF 的 LAYER 段中定义。
脚本输出一份检查报告,列出缺失项与统计结果,并以退出码区分是否通过。
环境与依赖
- Python 3.8+,仅使用标准库(
re、sys、argparse)。 - 输入:一个或多个 LEF 文件、一个 DEF 文件。LEF/DEF 为文本格式,脚本按段关键字解析。
实现思路
LEF 与 DEF 都是分段的文本格式,用行首关键字识别段。解析时维护“当前段”状态:进入 MACRO 段收集单元名与其中的 PIN 名;进入 LAYER 段收集层名。DEF 中 COMPONENTS 段每行给出实例名与单元名,PINS 段给出顶层引脚及其层,SPECIALNETS/NETS 段中的 ROUTED/NEW 行给出布线层。收集完成后做集合比对。
完整代码
import re
import sys
import argparse
def parse_lef(paths):
"""返回 (macros, macro_pins, layers)。
macros: {macro_name: set(pin_name)}
layers: set(layer_name)
"""
macros = {}
layers = set()
cur_macro = None
cur_pin = None
for path in paths:
with open(path, "r", errors="ignore") as f:
for raw in f:
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("MACRO "):
cur_macro = line.split()[1]
macros.setdefault(cur_macro, set())
cur_pin = None
elif line == "END" and cur_macro and cur_pin:
cur_pin = None
elif line.startswith("PIN ") and cur_macro:
cur_pin = line.split()[1]
macros[cur_macro].add(cur_pin)
elif line.startswith("LAYER "):
layers.add(line.split()[1])
elif line.startswith("END ") and cur_macro:
cur_macro = None
return macros, layers
def parse_def(path):
"""返回 (instances, top_pins, routed_layers)。
instances: {inst_name: cell_name}
top_pins: {pin_name: set(layer)}
routed_layers: set(layer)
"""
instances = {}
top_pins = {}
routed_layers = set()
section = None
cur_pin = None
with open(path, "r", errors="ignore") as f:
for raw in f:
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("COMPONENTS"):
section = "COMPONENTS"
continue
if line.startswith("PINS"):
section = "PINS"
continue
if line.startswith("SPECIALNETS") or line.startswith("NETS"):
section = "NETS"
continue
if line.startswith("END ") and section:
section = None
cur_pin = None
continue
if section == "COMPONENTS":
m = re.match(r"-\s+(\S+)\s+(\S+)", line)
if m:
instances[m.group(1)] = m.group(2)
elif section == "PINS":
m = re.match(r"-\s+(\S+)", line)
if m:
cur_pin = m.group(1)
top_pins.setdefault(cur_pin, set())
m = re.search(r"LAYER\s+(\S+)", line)
if m and cur_pin:
top_pins[cur_pin].add(m.group(1))
elif section == "NETS":
m = re.search(r"(?:ROUTED|NEW)\s+(\S+)", line)
if m:
routed_layers.add(m.group(1))
return instances, top_pins, routed_layers
def check(lef_paths, def_path):
macros, layers = parse_lef(lef_paths)
instances, top_pins, routed_layers = parse_def(def_path)
missing_cells = []
missing_pins = []
for inst, cell in instances.items():
if cell not in macros:
missing_cells.append((inst, cell))
continue
for pin in macros[cell]:
pass # 引脚存在性由 DEF 侧连接决定,见下
# 检查 DEF 中实例引用的单元是否存在
for inst, cell in instances.items():
if cell not in macros:
missing_cells.append((inst, cell))
# 检查顶层引脚引用的层
missing_layers = set()
for pin, used in top_pins.items():
for lyr in used:
if lyr not in layers:
missing_layers.add(lyr)
for lyr in routed_layers:
if lyr not in layers:
missing_layers.add(lyr)
return {
"macro_count": len(macros),
"layer_count": len(layers),
"inst_count": len(instances),
"missing_cells": missing_cells,
"missing_layers": sorted(missing_layers),
}
def main():
ap = argparse.ArgumentParser(description="LEF/DEF 合法性检查")
ap.add_argument("--lef", nargs="+", required=True, help="一个或多个 LEF 文件")
ap.add_argument("--def", dest="def_file", required=True, help="DEF 文件")
args = ap.parse_args()
r = check(args.lef, args.def_file)
print("=== LEF/DEF 检查报告 ===")
print(f"LEF 单元(MACRO)数: {r['macro_count']}")
print(f"LEF 布线层(LAYER)数: {r['layer_count']}")
print(f"DEF 实例数: {r['inst_count']}")
ok = True
if r["missing_cells"]:
ok = False
print(f"\n[FAIL] 缺失单元定义 {len(r['missing_cells'])} 处:")
for inst, cell in r["missing_cells"]:
print(f" 实例 {inst} 引用的单元 {cell} 未在 LEF 中定义")
else:
print("\n[PASS] 所有实例引用的单元均在 LEF 中定义")
if r["missing_layers"]:
ok = False
print(f"\n[FAIL] 缺失布线层定义 {len(r['missing_layers'])} 个:")
for lyr in r["missing_layers"]:
print(f" 层 {lyr} 未在 LEF 中定义")
else:
print("[PASS] 所有引用的布线层均在 LEF 中定义")
print("\n结论:", "通过" if ok else "不通过")
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()
运行方式
python check_lef_def.py --lef tech.lef cells.lef --def design.def
可传入多个 LEF(工艺库 + 单元库),脚本会合并解析。
验证方法与通过条件
- 运行后查看报告:若打印
[PASS]且结论为“通过”,退出码为 0,说明单元与布线层定义完整。 - 若存在缺失,报告会逐条列出缺失的单元(含实例名)或层名,退出码为 1。
- 可用
echo $?(Linux/macOS)或echo %ERRORLEVEL%(Windows)确认退出码。 - 构造一个 DEF 引用 LEF 中不存在的单元,脚本应报
[FAIL]并列出该实例,用于验证检查逻辑生效。
说明与边界
本脚本按段关键字做轻量文本解析,适用于标准 LEF/DEF 结构;对引脚完整性的检查聚焦于“实例引用的单元是否存在”,因为 DEF 中实例的引脚连接需结合 NETS 段解析,本案例将其作为扩展点。若需更严格的引脚级校验,可在 NETS 段解析每个网络的 (inst pin) 连接并比对 MACRO 的 PIN 集合。