nextpnr 命令返回非零退出码:布局布线失败速查
更新时间:2026-10-25。FAQ 速查页,面向用 Yosys + nextpnr 开源流程做 iCE40 / ECP5 / Gowin 开发的同学。典型场景:
make跑到布局布线这步,屏幕上滚过一大片日志,最后定格在make: *** [build/demo.asc] Error 1——退出码非零,但「为什么失败」淹没在前面的几百行输出里。nextpnr 是个命令行工具,它和所有 Unix 程序一样用退出码汇报结果:0 是成功,非零是失败,失败原因在标准输出/标准错误的日志里,而不在退出码本身。本篇只收「如何看懂退出码并顺藤摸瓜找到第一条 ERROR」;具体某一类报错的处置,见文末互链的各专题页。
本文要回答的问题
- nextpnr 退出码 1、134、139 各自代表什么?
- make 只报 Error 1,怎么把 nextpnr 的真实报错挖出来?
- 脚本里 set -e 一失败就退出,日志都来不及看怎么办?
- Makefile / shell / Python 三种调用方式下,如何正确捕获并判断返回值?
一、报错原文与现象
1. make 场景下最常见的定格画面:
nextpnr-ice40 --hx8k --package ct256 --json build/demo.json --pcf constraints/demo.pcf --asc build/demo.asc
...(几十行日志)...
make: *** [Makefile:23: build/demo.asc] Error 12. shell 脚本被 set -e 直接掐断:
+ nextpnr-ice40 --json build/demo.json --asc build/demo.asc
build.sh: line 12: nextpnr-ice40: command not found脚本在失败处戛然而止,后面的打包、烧录步骤全部不执行。
3. 崩溃型失败(退出码大于 128):
Segmentation fault (core dumped)
make: *** [Makefile:23: build/demo.asc] Error 139先建立一个关键认知:退出码只告诉你「死没死、怎么死的」,不告诉你「为什么死」。1 是 nextpnr 自己检出了错误(日志里必有 ERROR 行);134(128+6)是 abort,多为内部断言失败;139(128+11)是段错误,属于工具崩溃——后者见 nextpnr 段错误崩溃速查。另外 command not found 的退出码是 127,那不是布局布线失败,是 shell 根本没找到 nextpnr 命令——属于安装与 PATH 配置问题,排查思路见 nextpnr 找不到文件或路径速查 姊妹篇的 PATH 检查段落。
二、可能原因总览
| 退出码/现象 | 可能原因 | 处置 |
|---|---|---|
| Error 1,日志有 Failed to open input file | 输入 .json 网表缺失或路径错 | 见 路径不存在速查,先确认上游 yosys 成功 |
| Error 1,日志有 syntax / parse | 网表或约束文件语法非法 | 见 语法解析错误速查 |
| Error 1,日志有 resource / no available | 器件资源不够装下设计 | 见 资源超限速查 |
| Error 1,约束引脚冲突 | pcf 里两个信号绑同一引脚 | 检查约束文件,注释冲突行逐个恢复 |
| Error 134 / Aborted | 内部断言失败(多为损坏输入) | 重新生成网表,核对 nextpnr 与 yosys 版本 |
| Error 139 / Segmentation fault | 工具崩溃 | 见 段错误速查 |
| Error 127 | 命令不存在 | PATH 问题,非布局布线失败 |
| make 报 Error 但手动跑成功 | 工作目录或环境变量不一致 | 比较两种方式下的 pwd 与环境 |
三、逐条排查展开
第一步永远是「单独重跑,拿到完整日志」。 make 的输出被它和日志混在一起,还经常并行(-j)导致多目标输出交错。把失败命令原样拎出来单独跑一遍,并把完整输出落盘:
cd <工程目录>
nextpnr-ice40 --hx8k --package ct256 --json build/demo.json \
--pcf constraints/demo.pcf --asc build/demo.asc 2>&1 | tee /tmp/nextpnr-rerun.log
echo "退出码: $?" # 注意:管道后要查 ${PIPESTATUS[0]},bash 下 $? 是 tee 的管道场景下 $? 拿到的是管道最后一个命令(tee)的退出码,这是新手最常踩的坑。要么用 set -o pipefail 让管道整体返回失败码,要么查 ${PIPESTATUS[0]}。
第二步:抓第一条 ERROR,而不是最后一条。 日志里翻 grep -n "ERROR" /tmp/nextpnr-rerun.log,从第一条开始看。EDA 工具的报错有强烈的连锁效应:第一条 ERROR(比如打不开网表)会导致后续一串莫名其妙的次级报错,修最后一条毫无意义。第一条 ERROR 的行号、文件、器件资源名,就是本篇结尾互链各专题页的入口。
理解 set -e 的行为,别和它对抗。 构建脚本开头写 set -euo pipefail 是好纪律——任何一步失败立即终止,避免「上游已经失败、下游拿着半截产物继续跑」的假成功。但它带来的困扰是失败后现场消失。两个补救:
set -e
nextpnr-ice40 ... || { echo "nextpnr 失败,退出码 $?,日志见 build/nextpnr.log"; exit 1; }即:关键命令显式 || 捕获,把退出码和日志位置打印出来再退出,既不放弃 fail-fast,又保留现场。
Python / CI 调用时用 returncode 判断,别解析日志文本。 自动化脚本里常见错误是用「日志里有没有 ERROR 字样」判断成败——日志措辞随版本变,退出码才是契约:
import subprocess
r = subprocess.run(["nextpnr-ice40", "--json", "build/demo.json",
"--asc", "build/demo.asc"],
capture_output=True, text=True)
if r.returncode != 0:
print(f"nextpnr 失败,退出码 {r.returncode}")
print(r.stderr[-2000:]) # 只打日志尾部,别把几千行灌进 CI 报告「make 失败、手动成功」九成是环境不一致。 Makefile 里的相对路径以 make 的工作目录为基准;CI 里 sh -c 起的非登录 shell 不读 .bashrc,PATH 可能缺工具路径。对照两处的 pwd、which nextpnr-ice40、echo $PATH,差异点基本就是答案。
四、排查流程图
下面这张决策图把「拿到非零退出码之后」的判断顺序画成一条主干:先区分「工具没跑起来」和「工具跑了但报错」,再按退出码数值分流到具体专题。红色是需要先纠正认知的根因节点,蓝色是排查动作,绿色是可以收工的分支,橙色是修复动作。
读这张图抓住一条主线:任何非零退出码都先过「单独重跑 + 看数值」这两关,不要直接在 make 的混杂输出里大海捞针。127 和大于 128 的两个分支都不属于布局布线本身的问题,分流出去之后,剩下的退出码 1 才进入「抓第一条 ERROR → 对号入座」的主流程。
五、预防与纪律
- 构建脚本标配
set -euo pipefail:失败立即终止且管道不吞错误码,假成功比真失败更难查。 - 每个工具的日志独立落盘:
build/logs/yosys.log、build/logs/nextpnr.log分开存,失败邮件/告警直接附日志路径。 - 关键命令显式捕获退出码并打印:别依赖默认行为,失败时输出「退出码 + 日志位置」两要素。
- CI 里判断成败只看 returncode:不解析日志文本;日志只在失败时截取尾部归档。
- Makefile 每个目标失败即停:不要为了不中断批量构建而给命令加
-前缀吞掉错误——真想跑完全部目标收集错误,用make -k并保证最后统一汇报哪些目标失败。 - 定期演练:每月挑一次故意制造失败(删掉中间网表),确认告警能准确指出「哪一步、退出码多少、日志在哪」。
这一篇在知识体系里的位置
本篇是 FPGA 开源流程排错速查簇中「构建结果判读」节点的入口页:退出码 127 指向命令查找与 PATH 配置问题,退出码 1 按首条 ERROR 分流到 路径不存在、语法解析错误、资源超限 等专题,大于 128 的崩溃类转 段错误崩溃速查;退出码为 0 但产物文件疑似写坏(半截 .asc、对不上校验和)属于磁盘写入问题,可参照 Design Compiler 磁盘写满速查 的写入中断分析思路,系列其余篇目见 FPGA 知识版图。
一句话总结
nextpnr 返回非零退出码时,先把命令从 make 里拎出来单独重跑并用 tee 落盘完整日志(管道场景记得 pipefail 或 PIPESTATUS),再看退出码分流:127 是 PATH 问题、134/139 是工具崩溃、1 才是布局布线检出错误——对退出码 1 用 grep 抓第一条 ERROR 而非最后一条,按其类型进对应专题处置;脚本侧坚持 set -euo pipefail 加关键命令显式捕获打印「退出码 + 日志位置」,让每次失败都自带现场。