4、综合流程概览:从 RTL 到网表的完整流程,yosys 与 OpenROAD 的分工
说实话,很多刚接触 OpenROAD 的朋友,上来就想跑一个完整的流程。结果呢?要么是 yosys 报了一堆语法错误,要么是 OpenROAD 说找不到网表。我刚开始也踩过这个坑——以为装好工具就能一键生成版图。其实,你得先搞清楚这两兄弟到底谁负责什么。
这一章,我们就来聊聊从 RTL 到网表的完整流程。说白了,就是把你写的 Verilog 代码,变成工具能识别的逻辑网表。这个过程中,yosys 和 OpenROAD 各有各的活。咱们一个一个说。
4.1 综合流程的宏观视角
整个流程,我习惯把它分成两大阶段:逻辑综合和物理实现。逻辑综合归 yosys 管,物理实现归 OpenROAD 管。你想想看,这就像盖房子——先有设计图纸(RTL),然后算出需要多少砖头钢筋(逻辑网表),最后才去实地施工(物理设计)。
下面这张图,把整个流程串起来了。我建议你多看几眼,后面每个步骤都会反复提到。
4.2 yosys 在做什么?
yosys 的核心任务,就是把你的 RTL 代码翻译成工具能懂的网表。这个过程,我拆成三步:
- 读入 RTL:yosys 支持 Verilog 2001 和 SystemVerilog 的子集。我个人习惯把所有源文件写在一个列表里,然后用
read_verilog一次性读入。 - 逻辑综合:把高级的 always 块、assign 语句,转换成基本的逻辑门(AND、OR、NOT、DFF 等)。这一步,yosys 会做很多优化——比如常量传播、资源共享。
- 技术映射:把通用逻辑门,映射到目标工艺库里的标准单元。这一步,你需要提供 Liberty 格式的库文件。
核心输出:yosys 最终生成一个 Verilog 格式的网表文件(通常叫 synth.v)。这个网表里,只有标准单元实例和连线,没有 always 块了。
举个例子,你写了一个简单的加法器:
module adder (
input [3:0] a, b,
output [3:0] sum
);
assign sum = a + b;
endmodule
经过 yosys 综合后,它会变成一堆全加器和半加器的实例。你想想看,这个过程其实挺神奇的——一行 assign 语句,背后对应着几十个标准单元。
4.3 OpenROAD 在做什么?
OpenROAD 拿到 yosys 生成的网表后,就开始干物理实现的活了。它的流程,我习惯分成四个阶段:
| 阶段 | 英文名称 | 主要工作 |
|---|---|---|
| 1. 布图规划 | Floorplan | 确定芯片面积、IO 位置、宏单元摆放 |
| 2. 布局 | Placement | 把标准单元放到行里,优化线长和时序 |
| 3. 时钟树综合 | CTS | 构建时钟网络,保证时钟到达每个触发器的延迟一致 |
| 4. 布线 | Routing | 用金属层把标准单元连起来,满足 DRC 规则 |
每个阶段,OpenROAD 都会做时序检查和优化。我记得有一次,我在布局阶段发现 setup 违例,结果发现是 floorplan 时把两个关键模块放太远了。后来调整了一下位置,时序就收敛了。
注意:OpenROAD 不会重新综合你的逻辑。它只负责物理实现。如果你发现时序收敛不了,别指望 OpenROAD 能帮你改 RTL——那是 yosys 的活。
4.4 两者的分工与衔接
yosys 和 OpenROAD 的分工,说白了就是一句话:yosys 管逻辑,OpenROAD 管物理。它们之间通过网表文件衔接。这个网表文件,我建议你好好检查一下——有没有未连接的端口?有没有悬空的线?
我曾经遇到过一个坑:yosys 综合出来的网表里,有一个模块的端口顺序和 RTL 里定义的不一样。结果 OpenROAD 读进去后,连线全乱了。后来我养成了一个习惯——每次综合完,先用 verilog -lint 检查一遍网表。
下面这个表格,把两者的职责对比得更清楚:
| 对比项 | yosys | OpenROAD |
|---|---|---|
| 输入 | RTL 代码(.v/.sv) | 逻辑网表(.v)+ 物理约束 |
| 输出 | 逻辑网表(.v) | GDSII 版图 |
| 核心操作 | 逻辑优化、技术映射 | 布局、布线、时钟树 |
| 时序分析 | 静态时序估算 | 带寄生参数的精确 STA |
| 工艺依赖 | 需要 Liberty 库 | 需要 LEF/DEF + 工艺文件 |
4.5 一个完整的综合命令流
说了这么多,不如直接看命令。下面是我常用的 yosys 脚本:
# yosys 综合脚本
read_verilog -sv src/top.sv
read_liberty -lib lib/sky130_fd_sc_hd__tt_025C_1v80.lib
synth -top top
dfflibmap -liberty lib/sky130_fd_sc_hd__tt_025C_1v80.lib
abc -liberty lib/sky130_fd_sc_hd__tt_025C_1v80.lib
write_verilog output/synth.v
stat
然后,OpenROAD 读取这个网表:
# OpenROAD 脚本片段
read_liberty lib/sky130_fd_sc_hd__tt_025C_1v80.lib
read_verilog output/synth.v
link_design top
read_sdc constraints/top.sdc
initialize_floorplan -die_area "0 0 100 100" -core_area "10 10 90 90"
place_global
place_detail
clock_tree_synthesis
route_global
route_detail
write_gds output/top.gds
小技巧:我建议你把 yosys 和 OpenROAD 的命令分别写成两个脚本文件。这样调试的时候,可以单独跑综合,不用每次都从头开始。另外,stat 命令会输出面积和时序的统计信息,我每次都会看一眼——如果面积比预期大很多,说明 RTL 可能写得太冗余了。
4.6 避坑指南
最后,分享几个我踩过的坑:
- 库文件版本不一致:yosys 用的 Liberty 库和 OpenROAD 用的 LEF 库,必须来自同一个工艺版本。我见过有人混用了不同版本的库,结果 OpenROAD 报了一堆尺寸不匹配的错误。
- 忘记加时序约束:有些朋友在 yosys 阶段不设时序约束,觉得 OpenROAD 会自己处理。其实,yosys 在做逻辑优化时,也需要知道时钟周期——否则它不会做面积和速度的权衡。
- 网表里有黑盒子:如果 RTL 里例化了某个模块,但 yosys 找不到它的定义,就会生成一个黑盒子。OpenROAD 读进去后,这个模块的引脚会全部悬空。嗯,这个问题我排查了整整一个下午才找到原因。