ngspice unknown subckt 报错:X 元件实例化找不到 .subckt 定义的排查与修复
更新时间:2026-11-11。长尾速查篇,属于 EDA 报错排查系列,汇总入口见 开源 EDA 报错排查导航。网表里写了一行
X1 in out amp实例化子电路,ngspice 批处理跑下去只回一句Error: unknown subckt: x1 in out amp就中止。子电路名明明写在网表里,为什么仿真器说不认识?本文用一份缺定义的最小网表把这条 ngspice unknown subckt 报错跑透。
首屏答案:SPICE 里以 X 开头的元件是子电路实例,它本身不带任何电路结构,只是按名字去查一张 .subckt 定义注册表;ngspice 展开网表时,在当前文件以及 .include/.lib 引入的全部文件里都找不到名为 amp 的 .subckt 定义,查表失败便抛出 unknown subckt、退出码 1。本例 bad.cir 只有实例没有定义、也没有 include;修复方式是加一行 .include amp.cir 把定义文件引进来,复跑 .op 工作点分析正常输出、退出码 0。
这张漏斗对应本文的排查顺序:先确认报错发生在网表展开期而非数值求解期,再核对 X 行引用的子电路名与实际定义是否对得上,最后才追文件加载链。跳过定义与 include 核对、直接怀疑器件模型或仿真器版本,是这类报错最常见的弯路。
本文要回答的问题
- 网表里实例化了一个没有定义的子电路,ngspice 的完整报错原文是什么,退出码是多少?
unknown subckt具体指什么,X 元件和 R、C 这类基础元件在解析上有什么不同?- 报错后面那条
incomplete or empty netlist ... no simulations run!是另一个独立故障吗? - 正确的修复写法是什么,复跑要看到哪些输出才算闭环?
一、完整报错原文
最小复现工程三个文件。坏网表 bad.cir 在第 3 行用 X1 in out amp 实例化一个名叫 amp 的子电路,但整个文件里没有任何 .subckt amp ... .ends 定义,也没有引入外部文件:
Missing subcircuit test
Vin in 0 DC 1
X1 in out amp
R1 out 0 1k
.end在 macOS 本机用批处理模式跑 ngspice -b bad.cir,完整输出原样摘录如下:
--- 1. missing subcircuit definition ---
$ ngspice -b bad.cir
Error: unknown subckt: x1 in out amp
in line no. 3 from file bad.cir
Simulation interrupted due to error!
Error: incomplete or empty netlist
or no ".plot", ".print", or ".fourier" lines in batch mode;
no simulations run!
Note: No compatibility mode selected!
Circuit: missing subcircuit test
exit code: 1报错信息要分两段读。第一段 Error: unknown subckt: x1 in out amp 是根诊断,并且明确给了行号——bad.cir 第 3 行,正是 X 实例所在的那一行;注意 ngspice 把实例名规范化成了小写 x1,SPICE 的网表名字大小写不敏感,对照源码时不用疑惑。第二段 incomplete or empty netlist ... no simulations run! 是级联提示,不是第二个独立故障:网表展开在第 3 行就中断了,后续没有任何分析语句被执行,批处理模式又要求至少有 .plot、.print 或 .fourier 产出,仿真器收尾时发现一轮分析都没跑,便补了这条说明。真正要修的只有前面那一条。
二、工具与版本
本次实验在 macOS 本机完成,ngspice 为 ngspice-47 构建,线性求解器带 KLU,可执行文件位于 /usr/local/bin/ngspice,与运行日志头部记录一致;两次运行都走 -b 批处理模式,不进交互界面。
版本信息先固定,因为 unknown subckt 的诊断措辞随版本演进,老版本可能不带行号。本文的报错原文与退出码只对 ngspice-47 这个构建负责,换大版本时应保留当次完整输出再对照。这条报错发生在网表展开阶段,矩阵方程尚未组装,与 KLU 求解器、牛顿迭代和收敛性无关,数值内核原理可参考 SPICE 电路仿真器:它在解什么方程、牛顿迭代怎么走。
三、根本原因:X 元件只是引用,定义必须先注册
SPICE 的基础元件(R、C、L、V、E 等)在元件行内就携带全部参数,解析器读到即可入账;以 X 开头的行则完全不同,它是一个子电路实例,语法是 X<实例名> <节点列表> <子电路名>,本身不描述任何器件,只做一件事:拿着最后的子电路名去查注册表。注册表的条目同样只有两个来源——当前网表内的 .subckt <名字> ... .ends 块,以及 .include/.lib 语句从外部文件加载进来的同名块。ngspice 展开到 X1 in out amp 时在注册表里查不到 amp,实例无法展开,立刻报错中止。
图中蓝色是网表解析动作,紫色是注册表,灰色 include 链是注册表的外部供给通道,红色是本例实际走到的故障支路,绿色支路只有定义可见时才会发生。三类常见写法都会踩到同一条报错:压根没写定义;定义在独立文件里却漏了 .include,同目录下的文件不会被自动加载;include 了但名字对不上,包括拼写差异与 .lib 调库时漏写入口名。排查只有一个方向:顺着注册表的两个来源逐条核对。
四、排查清单:先对名字,再查加载链
图中红色是入口故障,蓝色是信息收集动作,灰色是加载链判定,橙色是两类修复分支,绿色是闭环。对照这张图,落地清单如下:
- 先读 X 行的最后一个 token:
X1 in out amp里in、out是节点,真正被引用的子电路名是行尾的amp,报错原文会把整行回显,先把这个名字圈出来。 - 在当前网表里查定义块:
grep -n '^\.subckt' bad.cir列出全部本地定义,确认有没有.subckt amp;本例输出为空,本地无定义。 - 核对 include 与 lib 链:有外部文件时确认
.include的路径相对于 ngspice 的工作目录成立,.lib调用还要带上库文件内定义的入口名;文件存在不等于会被加载,缺 include 时 ngspice 不会扫描目录。 - 对齐名字拼写:SPICE 网表名统一折成小写匹配,
Amp、AMP都会折成同一个键,但amp1与amp就是两个名字;子电路改名后,所有 X 实例行尾必须同步改。 - 检查定义块自身闭合:
.subckt必须有配对的.ends,定义块语法残缺时该条目也注册失败,现象同样是 unknown subckt。 - 脚本判退出码:批处理脚本不要只看有没有出曲线,ngspice 解析失败即返回非零,流水线应据此中止,避免拿空结果进入后续处理。
失败路径的终端记录如下,注意网表在第 3 行展开时即中断,没有任何分析阶段的输出:
五、修复代码与复跑确认
子电路定义本来就写在 amp.cir 里,内容是一个带输入电阻、反馈电阻和压控电压源的放大器:
.subckt amp in out
Rin in 0 10k
Rf in out 100k
Eamp out 0 in 0 10
.ends修复只动网表的加载语句:在 fixed.cir 顶部加一行 .include amp.cir,把定义文件在实例化之前引入,并补上 .op 工作点分析让批处理有明确产出:
Subcircuit include test
.include amp.cir
Vin in 0 DC 1
X1 in out amp
R1 out 0 1k
.op
.end同一条批处理命令跑修复后的网表,网表展开顺利通过,.op 完成并打印工作点结果,进程以退出码 0 结束:
--- 2. fixed: .include amp.cir provides the subckt ---
$ ngspice -b fixed.cir
p -1e-05
Total analysis time (seconds) = 0.000407
Total elapsed time (seconds) = 0.008
Total DRAM available = 16384.000 MB.
DRAM currently available = 6699.602 MB.
Maximum ngspice program size = 4.570 MB.
Current ngspice program size = 4.570 MB.
exit code: 0输出里的 p = -1e-05 是输入端电压源 Vin 的工作点功率,负号表示电压源向外电路送出功率,量级与 1V 驱动、十千欧级电阻的网络吻合,说明子电路确实被完整展开、求解器跑完了真实分析。修复路径的终端记录如下,与失败段形成退出码 1 对 0 的闭环对照:
判定修复成立看两个证据。其一,退出码为 0 且出现了真实的工作点数值与分析耗时,证明网表展开和数值求解两个阶段都走完了;其二,修复手段只是补加载语句,没有删改 X 实例行,可见问题确实出在定义的可见性而非实例写法本身。如果补了 include 仍报同一个名字,回到第四节的清单继续查路径和拼写,不要用调换节点顺序之类与注册表无关的改动去试错。
实验环境
- 机器/系统:macOS 本机,ngspice 批处理模式(
-b) - 工具版本:ngspice-47(KLU),可执行文件
/usr/local/bin/ngspice - 日期:2026-11-11
- 完整命令:在实验目录执行
bash run.sh,脚本先运行ngspice -b bad.cir复现 unknown subckt 报错(退出码 1),再运行ngspice -b fixed.cir验证.include amp.cir后的工作点分析(退出码 0),逐条打印退出码。
代码获取
完整源码与复现脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-ngspice-subckt-not-found,含 bad.cir、fixed.cir、amp.cir 与 run.sh。本地装有 ngspice 后,在该目录执行 bash run.sh 即可原样复现「缺定义报错、补 include 复跑通过」两步运行。
这一篇在知识体系里的位置
本篇属于 开源 EDA 报错排查导航 的仿真网表加载分支,回答「子电路名写在网表里为什么仿真器看不见」。它与同簇的「引用了没被加载的定义」类问题是同一个家族:编译型工具靠显式输入文件建立符号表,SPICE 靠 .subckt 块与 include 链建立子电路注册表,排查方向都是先查定义是否存在、再查加载通道是否接通。数值求解阶段的收敛与矩阵问题属于另一个层面,机制见 SPICE 电路仿真器:它在解什么方程、牛顿迭代怎么走;全系列入口见 EDA 报错排查系列。
总结
ngspice unknown subckt 的本质是子电路注册表查表失败,而不是器件模型或求解器问题:X 元件行只携带节点和子电路名,ngspice 展开网表时必须在当前网表或 .include/.lib 引入的文件里找到同名 .subckt 定义。本例 bad.cir 第 3 行 X1 in out amp 无定义、无 include,ngspice-47 批处理报 Error: unknown subckt: x1 in out amp 并以退出码 1 中止,随后那条 no simulations run 只是分析未执行的级联提示。排查按「核对 X 行子电路名、查本地 .subckt 块、查 include 加载链、对齐拼写与闭合」四步走;修复只需在网表顶部补 .include amp.cir,复跑 .op 输出 p = -1e-05、退出码 0。让定义文件显式进加载链、脚本严格判退出码,即可避免同类问题流入后续分析。