ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从 RTL 到 GDSII:个人开发者用开源工具链实现 ASIC 芯片设计全流程

从 RTL 到 GDSII:个人开发者用开源工具链实现 ASIC 芯片设计全流程 在硬件爱好者圈子里最近讨论度比较高的一个内容是 bitluni 分享的“我第一次认真做的 ASIC 项目”。视频标题里的“熟肉”意味着创作者或社区为它配了中文字幕对国内喜欢数字电路、Verilog 和芯片设计的朋友非常友好。很多人看完第一反应是原来个人开发者现在也能完整走一遍 ASIC 流程而不只是停留在 FPGA 开发板上点灯。这篇文章我想结合这个项目引发的热度系统整理一份从 0 开始做 ASIC 项目的经验教程。内容包括 ASIC 的核心概念、开源工具链的选择、环境搭建、RTL 设计、仿真验证、使用 OpenLane 完成从综合到 GDSII 的全流程以及常见报错排查思路。无论你是刚接触硬件描述语言的新手还是已经在 FPGA 上有一定基础、想往流片方向尝试的开发者都可以跟着这篇文章走一遍。1. ASIC 项目到底是什么个人做芯片的门槛有多高1.1 视频里的第一次为什么它会引发共鸣bitluni 的视频标题有一个关键词“第一次认真做”。这句话很能说明问题。在大多数人的认知里ASICApplication-Specific Integrated Circuit专用集成电路是芯片大厂才敢碰的东西动辄几百万研发费用、几十人团队、一年左右开发周期个人完全不敢想象。但视频展示的是一个完全不同的流程从零编写 RTL 代码、写 testbench、跑仿真、交给开源工具链完成自动布局布线、最后把设计提交到多项目晶圆MPW流片渠道得到一颗真实可测试的芯片。整个过程虽然也有波折但不再高不可攀。这种“第一次”记录的珍贵之处在于它把芯片设计从 PPT 和课本拉回了桌面。对读者来说跟着视频学到的不是某个孤立的技术点而是一整套可以在自己电脑上复现的 ASIC 设计流程。1.2 ASIC 与 FPGA、CPU 的区别先理清几个容易混淆的概念。ASIC是为特定任务定制的芯片。它在出厂时逻辑就固定了不能再改。优点是面积小、功耗低、批量成本低缺点是开发周期长、流片费用高、一旦改需求就要重新做版图。FPGAField-Programmable Gate Array现场可编程门阵列是一种可重构硬件。芯片内部有大量查找表和可编程互连设计者通过配置文件改变逻辑功能。开发灵活、迭代快适合原型验证和小批量场景但单颗成本高、功耗大。CPU是一种通用的指令执行引擎。它通过软件指令完成不同任务灵活性最高但针对某个特定任务的性能密度不一定比得上专用硬件。理解这三者关系之后你就会发现 ASIC 项目的核心思路是把“能用软件跑”或“能用通用硬件跑”的功能用专用逻辑固定下来以换取性能、功耗和成本优势。bitluni 的项目也体现了这个思路——他为自己的应用场景设计了一颗专用的小芯片。1.3 开源 PDK 与 MPW 渠道个人做 ASIC 的路径个人做 ASIC 能跑通主要靠两件事第一开源 PDK。PDKProcess Design Kit工艺设计套件是代工厂提供的工艺文件集合包括晶体管模型、设计规则、标准单元库、IO 库等。过去 PDK 需要签 NDA 才能拿到个人基本没机会。现在 SkyWater 130nm、GF 180nm 等工艺节点已经开放了开源 PDK配合 OpenLane、OpenROAD 等开源 EDA 工具个人可以在本地完成完整的数字后端流程。第二MPWMulti-Project Wafer多项目晶圆渠道。MPW 把多个用户的设计拼在同一片晶圆上共同分摊流片成本。对于教学和个人项目全球已经有不少公开的 shuttle 项目例如 TinyTapeout、Efabless Caravel 等。你只需要交付 GDSII 文件和必要的说明后续封装、测试板等工作都有社区样板可以参考。这也就解释了为什么一个独立创作者能用“第一次认真做”的态度去完成 ASIC 项目。工具链问题已经被开源生态解决了一大半剩下的是个人的工程能力与耐心。2. ASIC 设计流程核心概念从 RTL 到 GDSII2.1 RTL硬件功能的“源代码”RTLRegister Transfer Level寄存器传输级是数字芯片设计的起点。常用的硬件描述语言是 Verilog 和 VHDL其中 Verilog 语法简洁、资料多是开源社区的主流选择。RTL 描述的是“数据在寄存器之间如何传输、如何被组合逻辑处理”。它虽然是代码形式但本质上是硬件结构。写 RTL 时脑子里必须有一张电路图哪些信号是触发器输出哪些信号是组合逻辑输出时钟沿到来时哪些寄存器会更新。// 文件路径src/pwm_led.v // 一个简单的可综合 Verilog 模块示例 module pwm_led ( input wire clk, input wire rst_n, output wire led ); reg [7:0] duty; reg [7:0] counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin duty 8d128; counter 8d0; end else begin counter counter 1b1; end end assign led (counter duty) ? 1b1 : 1b0; endmodule上面这个模块就是一个 PWM 输出用来驱动 LED 呼吸效果。所有逻辑都是可综合的可以最终变成真正的硬件电路。2.2 综合、布局布线、物理验证从 RTL 到芯片版图主要经过以下几个阶段逻辑综合Synthesis把 RTL 代码映射到标准单元库。标准单元是预先设计好的逻辑门例如与非门、触发器等。综合工具负责选择合适的单元、确定逻辑连接并生成门级网表netlist。布局规划与布局Floorplan Placement决定各个模块在芯片上的位置再把标准单元摆放到具体坐标上。优化目标是减少线长、避免拥塞、满足时序。时钟树综合Clock Tree Synthesis为了让时钟信号几乎同时到达所有触发器需要插入缓冲器构建时钟树。如果时钟偏斜过大芯片时序会出问题。布线Routing把单元引脚之间用金属层连接起来。布线既要满足工艺设计规则又要尽量减少信号干扰。物理验证包括 DRCDesign Rule Check设计规则检查和 LVSLayout vs. Schematic版图与原理图一致性检查。DRC 检查版图是否满足工艺规则例如金属线最小宽度、间距LVS 检查最终版图是否和门级网表一致。2.3 开源工具链 OpenLane 的作用OpenLane 是一个完整的开源 RTL-to-GDSII 流程内部串联了多个开源工具Yosys逻辑综合。OpenROAD布局布线、时钟树综合、时序优化。Magic、KLayout版图查看与 DRC。NetgenLVS。OpenLane 把这些工具封装成了统一的配置和流水线用户只需要提供 RTL 文件和一个配置文件就能跑完整个后端流程。bitluni 的项目大概率也是基于这类工具链完成的。对于个人开发者来说OpenLane 是目前最友好的入门选择。2.4 完整流程全景用一张简表来概括整个数字 ASIC 设计流程阶段输入输出关键工具RTL 设计需求文档Verilog/VHDL 源码文本编辑器、VSCode功能仿真RTL 源码 testbench仿真波形Verilator、Icarus Verilog逻辑综合RTL PDK 标准单元库门级网表Yosys布局布线门级网表 约束DEF、版图数据OpenROAD时钟树综合布局后网表CTS 后网表OpenROAD物理验证最终版图DRC/LVS 报告Magic、KLayout、Netgen生成 GDSII版图数据交付代工厂的文件KLayout、Magic每一步都可能出现迭代回退。比如布局布线后时序不满足可能要回到 RTL 修改逻辑或者调整综合约束而不是在后端硬扛。3. 环境准备把开源芯片工具链跑起来3.1 操作系统与硬件建议OpenLane 官方推荐在 Linux 环境下运行最常见的是 Ubuntu 22.04/24.04。如果你使用的是 Windows建议通过 WSL2 安装 Ubuntu或者使用虚拟机。macOS 也能装但 Docker 性能和文件系统兼容性需要注意。硬件方面做小规模 ASIC 设计其实不需要很夸张的配置。16GB 内存起步会比较舒服CPU 核心数越多OpenLane 跑综合和布局布线越快。如果是 32 核 64GB 内存的机器处理 TinyTapeout 规模的设计完全没有压力。如果配置较低可以先跑一个极小的模块例如本文的 PWM 呼吸灯也能完整走通流程。另外磁盘空间建议预留至少 30GB。OpenLane Docker 镜像、PDK 文件和工作目录加起来会占用不少空间。3.2 安装 Docker 与 OpenLaneOpenLane 官方推荐使用 Docker 方式运行好处是环境隔离、版本可控不会污染宿主机。先确认 Docker 已经安装docker --version然后从 GitHub 克隆 OpenLane 仓库。git clone https://github.com/The-OpenROAD-Project/OpenLane.git cd OpenLane不同版本的 OpenLane 安装方式有差异。以常见版本为例通常需要执行make mount make flowmake mount会拉取 OpenLane 的 Docker 镜像并进入容器环境。第一次拉取镜像时耗时较长取决于网络环境。这里需要说明OpenLane 版本迭代较快建议以你 clone 的仓库中 README 和 Makefile 的说明为准。本文的配置示例是一般性流程应用到实际项目时请按官方文档调整命令和配置参数。3.3 安装仿真验证工具链在做 ASIC 之前仿真验证是必须认真对待的一步。推荐安装以下几种工具Verilator高性能 Verilog 仿真器支持将 Verilog 编译成 C 模型适合跑较大规模仿真。Icarus Verilogiverilog轻量级仿真器搭配 GTKWave 查看波形适合学习和小模块验证。GTKWave波形查看器读取 VCD/FST 波形文件。Ubuntu 下安装sudo apt update sudo apt install verilator iverilog gtkwave3.4 项目目录结构为了方便后续 OpenLane 流程的运行建议统一目录结构。下面是我比较推荐的一种asic-pwm-project/ ├── src/ │ └── pwm_led.v ├── tb/ │ └── tb_pwm_led.v ├── simulation/ │ └── run_sim.sh └── openlane/ ├── config.tcl └── runs/src存放 RTL 源码。tb存放 testbench。simulation存放仿真脚本和波形输出。openlane存放 OpenLane 配置文件与运行结果。4. 从零写第一个 ASIC 模块PWM 呼吸灯4.1 需求与模块规划先确定一个足够简单、又包含时序逻辑和组合逻辑的功能。PWM 呼吸灯非常适合输入50MHz 系统时钟也可以降低频率便于仿真。输入异步复位低有效。输出一个 LED 引脚通过 PWM 占空比变化产生呼吸效果。为了在真实硬件上观察呼吸周期不宜太快也不宜太慢。假设我们希望约 2 秒完成一次从暗到亮再到暗的过程。简单实现思路是两层计数器高频计数器产生 PWM 周期例如 1kHz。低频计数器控制占空比每个 PWM 周期后递增或递减。4.2 Verilog 源码实现下面是改进后的可综合 Verilog 代码包含两个计数器。// 文件路径src/pwm_led.v module pwm_led ( input wire clk, // 50MHz 时钟 input wire rst_n, // 异步复位低电平有效 output wire led // LED 输出 ); parameter CLK_FREQ 50_000_000; parameter PWM_FREQ 1_000; // PWM 频率 1kHz parameter BREATH_PERIOD 20; // 每个 PWM 周期步进一次20ms 步进 localparam PWM_CNT_MAX CLK_FREQ / PWM_FREQ - 1; // 49999 localparam STEP_CNT_MAX PWM_CNT_MAX / BREATH_PERIOD; // 约 2499 reg [15:0] pwm_cnt; reg [15:0] step_cnt; reg [7:0] duty; reg duty_dir; wire pwm_done (pwm_cnt PWM_CNT_MAX); wire step_done (step_cnt STEP_CNT_MAX); always (posedge clk or negedge rst_n) begin if (!rst_n) begin pwm_cnt 16d0; end else if (pwm_done) begin pwm_cnt 16d0; end else begin pwm_cnt pwm_cnt 1b1; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin step_cnt 16d0; end else if (pwm_done) begin if (step_done) begin step_cnt 16d0; end else begin step_cnt step_cnt 1b1; end end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin duty 8d0; duty_dir 1b1; // 1 表示渐亮 end else if (pwm_done step_done) begin if (duty_dir 1b1) begin if (duty 8d255) begin duty_dir 1b0; duty duty - 1b1; end else begin duty duty 1b1; end end else begin if (duty 8d0) begin duty_dir 1b1; duty duty 1b1; end else begin duty duty - 1b1; end end end end assign led (pwm_cnt duty) ? 1b1 : 1b0; endmodule这段代码在真实芯片上会综合成一组计数器、比较器和触发器。写 RTL 时要特别注意always块中的变量要么是纯时序逻辑要么是纯组合逻辑尽量避免在同一个块里混用不同赋值方式否则综合出的电路可能和仿真不一致。4.3 testbench 编写写 testbench 时一方面要模拟输入时钟和复位另一方面要观察输出波形是否符合预期。由于真实 50MHz 频率下呼吸周期太长仿真时可以把模块重新参数化或者直接缩短运行时间。这里我为了观察方便在 testbench 里用parameter覆盖模块参数。// 文件路径tb/tb_pwm_led.v timescale 1ns / 1ps module tb_pwm_led; reg clk; reg rst_n; wire led; // 为了方便仿真把时钟频率压低 pwm_led #( .CLK_FREQ(1000), .PWM_FREQ(100), .BREATH_PERIOD(10) ) u_dut ( .clk(clk), .rst_n(rst_n), .led(led) ); initial begin clk 0; rst_n 0; #100; rst_n 1; #100_000; $finish; end always #5 clk ~clk; // 100MHz 仿真时钟周期 10ns // 打印亮度变化相关信息 integer cycle_count; always (posedge clk) begin cycle_count cycle_count 1; if (cycle_count % 1000 0) begin $display(t%0t cycle%0d led%b, $time, cycle_count, led); end end initial begin $dumpfile(tb_pwm_led.vcd); $dumpvars(0, tb_pwm_led); end endmodule4.4 使用 Verilator 和 GTKWave 验证这里先使用 Icarus Verilog 做快速仿真因为命令更简单。cd simulation iverilog -o tb_pwm_led.vvp ../src/pwm_led.v ../tb/tb_pwm_led.v vvp tb_pwm_led.vvp运行后可以看到输出。然后打开波形gtkwave tb_pwm_led.vcd在 GTKWave 中把clk、rst_n、duty、pwm_cnt、led加到波形窗口观察duty是否会周期性变化。如果duty从 0 逐渐增加再逐渐减小就说明呼吸灯逻辑正确。如果使用 Verilator可以写一个简单的顶层 C 文件但学习阶段用 Icarus 更快。等你的设计规模变大、仿真时间很长时再切换到 Verilator 提升速度。功能仿真通过之后RTL 设计阶段才算告一段落。接下来才是 ASIC 流程真正开始的地方。5. 使用 OpenLane 完成从 RTL 到 GDSII5.1 创建设计配置OpenLane 需要你为每个设计创建一个配置文件。对于小规模 MPW 项目配置文件通常包括设计名、RTL 文件路径、时钟定义、面积约束、IO 约束等。在openlane/config.tcl中编写如下内容# 文件路径openlane/config.tcl set ::env(DESIGN_NAME) pwm_led set ::env(VERILOG_FILES) \ $::env(DESIGN_DIR)/../src/pwm_led.v set ::env(CLOCK_PORT) clk set ::env(CLOCK_PERIOD) 20 ;# 50MHz 对应 20ns 时钟周期 set ::env(DIE_AREA) 0 0 300 300 set ::env(FP_CORE_UTIL) 30 set ::env(PL_TARGET_DENSITY) 0.35 set ::env(IO_PINS) { clk 0 rst_n 1 led 2 }说明一下关键参数DESIGN_NAME顶层模块名必须和 RTL 中的module pwm_led一致。VERILOG_FILES告诉工具链 RTL 文件在哪。CLOCK_PORT/CLOCK_PERIOD定义时钟端口和周期。综合和布局布线会以此为约束目标。DIE_AREA芯片面积大小。面积设太大浪费设太小会布线拥塞。先从合适值开始跑完一轮看时序和利用率再调整。FP_CORE_UTIL和PL_TARGET_DENSITY标准单元核心利用率目标。IO_PINS输入输出引脚分配。不同 MPW 渠道对于引脚位置有不同要求如果你是跑 TinyTapeout需要按照其文档定义引脚。5.2 运行 OpenLane 流程进入 OpenLane 目录启动 Docker 容器后运行make mount进入容器后cd /home/openlane ./flow_tcl.tcl -design /path/to/openlane/config.tcl不同版本命令不同有些是基于 Python 的openlane --flow openlane /path/to/openlane/config.tcl如果流程成功会在runs目录下生成完整报告和版图文件。主要产出包括逻辑综合网表、布局布线后的网表、时序报告、DRC 报告、LVS 报告以及最终的 GDSII 文件。这里需要提醒一下OpenLane 首次运行需要下载 PDK 和标准单元库时间比较长。而且不同版本的 OpenLane 对配置参数的要求不同如果提示某个配置项不存在请先查阅对应版本的文档不要盲目照搬旧教程。5.3 检查时序与面积报告跑完流程后重点关注以下文件runs/时间戳/reports/synthesis/1-synthesis.sta.rpt综合后的静态时序分析报告。runs/时间戳/reports/cts/cts.sta.rpt时钟树综合后的时序报告。runs/时间戳/reports/routing/route.sta.rpt布线后的时序报告。runs/时间戳/reports/synthesis/1-synthesis.area.rpt面积报告。时序报告中会列出 WNSWorst Negative Slack最差负时序裕量和 TNSTotal Negative Slack总负时序裕量。WNS 为正值说明满足时序约束为负值说明路径太慢需要优化。如果是 PWM 呼吸灯这种简单逻辑50MHz 约束通常很容易满足。真正遇到时序违例大多是在更复杂的设计中。我在下一节会给出排查思路。5.4 生成 GDSII 与交付物流程全部通过后GDSII 文件位于runs/时间戳/results/final/gds/pwm_led.gds这个文件就是可以提交给代工厂的最终版图。可以用 KLayout 打开查看klayout pwm_led.gds对于个人项目你还能看到整个芯片的版图轮廓、IO 焊盘位置和内部标准单元摆放。第一次看到自己写的 Verilog 变成版图那种感觉很奇妙。同时建议保存一份完整的流程报告提交 MPW 渠道时通常需要填写设计说明、引脚定义和仿真结果。不要把 GDSII 直接丢给对方文档越完整返工概率越低。6. 常见问题与排查思路6.1 常见报错表格问题现象常见原因解决思路OpenLane 启动失败Docker 镜像未拉取或版本不匹配重新执行 make mount检查网络综合后网表为空顶层模块名和 DESIGN_NAME 不一致检查 RTL module 名与配置时序违例 WNS 为负时钟约束过紧或逻辑路径过长放宽时钟周期、简化组合逻辑链布局布线拥塞严重面积设置过小或利用率过高增大 DIE_AREA降低 PL_TARGET_DENSITYDRC 报错引脚位置冲突或电源环设置问题检查 IO 约束和电源网络配置LVS 不一致网表与版图不匹配重新检查流程日志确认没有未连接引脚仿真和综合结果不一样使用了不可综合语法或未初始化寄存器写可综合代码复位初始化所有触发器6.2 功能仿真正确但综合后行为不对这是新手最容易遇到的一类问题。根本原因通常是代码中使用了可综合工具无法正确映射的写法例如在always块里使用initial初始化变量。使用#延时控制。组合逻辑中存在多个驱动源。在时序逻辑里 Latency 过低条件触发了竞争冒险。排查建议查看综合后的网表确认关键逻辑是否存在。对比 RTL 仿真和门级仿真波形定位第一个产生差异的时间点。检查每一个always块的敏感列表确保组合逻辑块不会产生锁存器。6.3 时序违例的通用排查步骤当时序报告出现负 slack 时按照下面顺序排查查看是哪条路径违例属于组合逻辑路径还是跨时钟域路径。确认时钟约束是否合理。不要故意把时钟周期设成不可能达到的值。检查代码中是否有多级串联的比较器或加法器考虑插入流水线寄存器。利用 OpenLane 报告的路径延迟找出关键路径上的主要延迟来源。如果实在无法收敛可以考虑降低时钟频率或者调整综合策略例如开启时序优化选项。6.4 芯片回来后不工作怎么办如果 Flow 全部通过、MPW 也成功流片但芯片回来测试不正常先不要慌。物理芯片的问题不只是逻辑问题还涉及封装、焊接、电源、时钟质量、IO 连接等。建议准备一块测试板遵循以下排查顺序检查电源是否正常芯片各电源引脚电压是否正确。检查复位信号是否干净上电后是否有毛刺。用示波器观察外部时钟输入确认时钟频率、电平、占空比符合预期。检查所有 IO 引脚是否按文档连接到对应位置特别是时钟、复位、输出。如果输出是 PWM 信号先测静态电平再观察波形。对于首次流片建议在芯片里额外加入一个环形振荡器或者简单的测试结构便于验证工艺和 IO 是否正常工作。很多开源 shuttle 项目都会要求或建议添加这类测试电路。7. 工程最佳实践与学习路线7.1 RTL 开发习惯做 ASIC 和写软件最大的不同是你要为代码的运行结果负责到“物理层”。因此 RTL 开发必须从一开始就有工程化思维模块命名清晰顶层模块只做实例化不写复杂逻辑。所有跨时钟域信号都要经过同步器处理避免亚稳态。所有寄存器都要有复位值避免上电后出现未知状态。组合逻辑块中确保每个分支都有赋值防止综合出锁存器。代码注释要写清楚设计意图方便后续自己和他人维护。一句话RTL 代码不只是给仿真器看更是给综合工具、后端工具和其他工程师看的。7.2 工具链与版本管理ASIC 工具链版本差异非常大。一篇教程里的命令在一年后可能就不适用了。因此强烈建议固定工具版本不要随手升级。在项目根目录用README记录 OpenLane、PDK、仿真工具的版本号。用 Git 管理 RTL、配置和脚本但不要把 OpenLane 生成的runs目录提交到仓库。配置文件中的路径尽量使用相对路径方便在不同机器间迁移。我的习惯是每次运行 OpenLane 前先查看仓库的 CHANGELOG 或 Release 页面确认配置项是否有破坏性变更。这比等报错再上网搜索效率高得多。7.3 从 Tapeout 到芯片测试如果你计划提交 MPW务必提前了解渠道的提交时间节点和设计约束。以 TinyTapeout 这类教学项目为例通常要求设计面积不能超过限定值。IO 引脚位置固定不支持自定义。需要提供 Verilog 源码和测试说明。有多个设计共享一条测试总线必须遵循总线协议。因此在写 RTL 之前先读清楚提交说明再开始设计。流水线后期发现引脚不符合规范往往只能重新做一轮后端流程。芯片回来后测试也是一门学问。建议准备一块简单的测试板把芯片所有引脚引出然后用逻辑分析仪或示波器观察输出。如果设计中有内部寄存器也可以通过 IO 复用方式把内部状态扫描出来。7.4 下一步学习路线如果你顺着这篇文章完成了第一个小芯片下一步可以继续深入学习 SystemVerilog 和更复杂的接口设计例如 AXI、SPI、I2C。掌握 Verilator 的 C 仿真环境搭建自动化验证平台。学习 DFTDesign for Test可测试性设计基础概念加入扫描链。阅读 OpenLane 的架构文档了解每个子工具的作用。研究更先进的工艺节点和设计方法比如低功耗设计、时钟门控。ASIC 设计是一门“越深入越觉得自己不会”的学科但正因为如此它才有足够强的吸引力。bitluni 的“第一次认真做”恰恰说明了一个道理不要等所有条件都完美才开始先跑通一个最小闭环再逐步优化。你也可以在自己电脑上把 RTL 变成真实芯片去感受那条从代码到硅片的完整链路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进