cocotb TOPLEVEL_LANG 配置错误:Skipping simulation、顶层找不到与 SIM 切换
cocotb 的 Makefile 只有三个配置变量最容易写错:TOPLEVEL_LANG 声明 HDL 语言,TOPLEVEL 声明 RTL 顶层模块名,SIM 选择仿真器。cocotb TOPLEVEL_LANG 配置错误的迷惑之处在于,写错后不一定是红底报错——语言标记不被接受时,make 可能退出码 0 却零测试执行。本篇在同一容器内逐个真实触发错配,保留原始输出,再修复复跑到 PASS=1。如果现象是测试模块根本没被发现,先读 cocotb no tests to run 排查;参数传递问题见 cocotb plusargs 传参失败。
本文要回答的问题
TOPLEVEL_LANG漏写、拼错或大小写不对,cocotb 2.1.0 分别会怎样?TOPLEVEL与 RTL 模块名对不上时,报错来自 cocotb 还是仿真器?make SIM=icarus能过,为什么make SIM=verilator在 make 解析阶段就停止?- Verilator 的
--timing与版本要求如何影响含延迟代码的编译? - 怎样确认修复后的绿灯是真的跑过测试,而不是又一次静默跳过?
这张漏斗把四类输出收敛到三个变量:前两层红色条带是写错变量值的直接后果,紫色条带是环境与变量组合不兼容,绿色条带是唯一的修复判据。读图时注意第一层最危险——它不是失败,而是“没跑”。
一、三种错配的完整报错原文
实验工程只有一个 4 位计数器 RTL 和一个检查复位与递增的 Python 用例,六组 Makefile 每次只改一处。以下终端卡摘自同一次 bash run.sh 的合并输出,连续覆盖语言错配、顶层错配和 Verilator 版本拦截:
终端卡里有三种截然不同的输出。把 TOPLEVEL_LANG 写成 vhdl 或大小写写错成 Verilog,Icarus 规则只打印一行 Skipping simulation as only Verilog is supported on simulator=icarus,BADLANG_EXIT=0、BADSPELL_EXIT=0——make 成功,测试数为零。把 TOPLEVEL 写成不存在的 counter_top,编译命令里出现 -s counter_top,iverilog 随即报 Unable to find the root module "counter_top" in the Verilog source.,并提示 Perhaps -s counter_top'' is incorrect?``,退出码为 2。第三种,make SIM=verilator 还没开始编译,cocotb 的 Makefile 在第 29 行直接 *** Stop:要求 Verilator 5.036 或更高,实际是 5.020。
同一次实验还回答了“漏写会怎样”:完全省略 TOPLEVEL_LANG 时(cases/nolang),cocotb 2.1.0 会根据 VERILOG_SOURCES 自动推断为 verilog,日志同样有 TESTS=1 PASS=1 FAIL=0。这与旧教程“必须手写”的说法不同,但自动推断只在源文件列表与语言一致时可靠,混合工程仍应显式声明。
二、工具与版本:先固定基线
所有结论只对下面这组版本负责。cocotb 安装在镜像的独立虚拟环境中,不激活时 cocotb-config 不在 PATH,Makefile 的 include $(shell cocotb-config --makefiles)/Makefile.sim 会先报另一种错,排查时不要把环境未激活与配置错配混为一谈。
| 组件 | 版本 | 在实验中的角色 |
|---|---|---|
| cocotb | 2.1.0 | 提供 Makefile.sim 规则与 VPI 运行时 |
| Icarus Verilog | 12.0 (stable) | SIM=icarus 的编译与运行器 |
| Verilator | 5.020 (Debian 5.020-1) | SIM=verilator 目标,被版本下限拦截 |
| Python | 3.12.3 | cocotb venv 解释器 |
| 容器系统 | Ubuntu 24.04.5 LTS | geek-eda:0.3-sta 镜像 |
统一在容器内复跑还有一个原因:不同发行版打包的 Verilator 版本差异很大,cocotb 对 Verilator 的最低版本要求也随版本变化。本机这组数字出现“5.020 被 2.1.0 拒绝”,不代表你的环境一定被拒;先跑 verilator --version 与 cocotb-config --version,再对照报错里的下限。
镜像里的 cocotb 不在系统 Python 中,所有 make 命令都要先执行 source /opt/venvs/cocotb/bin/activate。漏了这一步会先得到 make: cocotb-config: Command not found 一类报错,那是环境问题,不该按本篇的配置错配去修。判断顺序很简单:cocotb-config --version 能打印版本,再谈 Makefile 内容。
三、根本原因:三个变量分别作用于三个阶段
TOPLEVEL_LANG = verilog
VERILOG_SOURCES = $(PWD)/counter.v
TOPLEVEL = counter
COCOTB_TEST_MODULES = test_counter
include $(shell cocotb-config --makefiles)/Makefile.sim这五行正确配置分别经过三个阶段,错配症状也按阶段分发:
| 变量 | 作用阶段 | 写错后的真实表现 | 修复动作 |
|---|---|---|---|
TOPLEVEL_LANG | make 规则选择 | Icarus 规则判定“只支持 Verilog”,打印 Skipping,零测试,退出码 0 | 小写 verilog 或 vhdl,与仿真器能力匹配 |
TOPLEVEL | HDL 编译命令 | iverilog -s <name> 找不到 root module,退出码 2 | 与 RTL 中 module <name> 完全一致 |
SIM | make 解析阶段 | *** cocotb requires Verilator 5.036 or later, but using 5.020. Stop. | 升级 Verilator,或暂时用 SIM=icarus |
静默跳过的机理值得单独说清。cocotb 的 Makefile.icarus 用条件判断比较 TOPLEVEL_LANG 与 verilog:只要这个标记不是精确的小写 verilog(包括 Verilog、vhdl),仿真目标就被替换成一句 echo,results.xml 规则也不真正运行仿真。make 认为自己完成了工作,所以退出码是 0。CI 里若只看退出码,回归会在“全绿”中失去覆盖。因此判据必须是日志中的 TESTS=/PASS= 行,而不是 make 的返回值。
Verilator 一侧还有两个独立约束。其一是版本:cocotb 2.1.0 的 Makefile.verilator 写死 VLT_MIN := 5.036,版本比较在解析期完成,所以编译一步都不会发生。其二是时序:5.020 已支持 --timing,且默认启用——实验中的过程延迟 always #2 y = a; 不加任何参数即可编译;只有显式加 --no-timing 时才报 %Warning-STMTDLY: Ignoring delay on this statement due to --no-timing,并因警告升级为错误而退出码 1。若使用更老的 Verilator,含延迟代码通常需要在 COMPILE_ARGS 中显式追加 --timing;但在本机版本上,先越过 5.036 这道版本门才有讨论参数的资格。
还有一个混合语言边界:vhdl 这个取值本身不是拼写错误,错的是“vhdl 配 Icarus”这对组合。VHDL 工程应让 SIM 指向 GHDL,并设置 VHDL_SOURCES;若工程同时含 Verilog 与 VHDL,还需要混仿后端支持。同理,源文件列表变量也要与语言一致——语言写 verilog 却只填了 VHDL_SOURCES,推断规则与实际编译内容就会互相矛盾,症状依然可能是“什么都没跑”。
四、排查清单:按 make 输出分诊
排查 cocotb TOPLEVEL_LANG 配置错误时,从最外层输出向内收敛,每一步只改一个变量:
- 先看有没有
TESTS=行。没有但退出码 0,立刻 grepSkipping simulation——命中即查TOPLEVEL_LANG的拼写、大小写与仿真器能力是否匹配(Icarus 只支持 Verilog,VHDL 需 GHDL 等)。 - 有编译命令但报
Unable to find the root module,对照编译行-s后的名字与 RTL 源码module声明,逐字符检查TOPLEVEL,注意参数化模块例化名与模块名不是一回事。 - make 尚未打印任何编译命令就
*** Stop,读停止信息正文:Verilator 版本不足就升级,cocotb-config找不到则先激活安装 cocotb 的虚拟环境。 - 切换
SIM后重新make clean再编译,避免旧sim_build/里的产物干扰判断。 - 修复确认不以退出码为准,而以回归摘要行为准:必须出现
TESTS=1 PASS=1 FAIL=0 SKIP=0。
| 观察到的输出 | 失败阶段 | 首查位置 |
|---|---|---|
Skipping simulation as only Verilog is supported | 规则跳过 | TOPLEVEL_LANG |
Unable to find the root module "..." | HDL 编译 | TOPLEVEL 与模块名 |
requires Verilator x.y or later | make 解析 | SIM 与 Verilator 版本 |
STMTDLY ... due to --no-timing | HDL 编译 | Verilator timing 开关 |
有 running ... (1/N) 但断言失败 | 测试执行 | 不属于配置问题,查测试与 RTL |
最后一行区分了配置面与逻辑面:一旦日志进入 running,三行配置就已经全部通过,之后的失败应按测试断言排查,可参考 cocotb 断言失败与 testfailed。
五、修复代码与复跑确认
修复就是把第一节的三组错配全部对齐到正确 Makefile(见本节开头的五行版本):语言写小写 verilog,顶层写 RTL 真实模块名 counter,仿真器用环境满足要求的 SIM=icarus。运行命令固定为:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta \
bash -lc 'source /opt/venvs/cocotb/bin/activate && make SIM=icarus'复跑后正确用例的回归摘要如下:
0.00ns INFO cocotb.regression running test_counter.counter_increments_after_reset (1/1)
** TESTS=1 PASS=1 FAIL=0 SKIP=0 61.00 0.00 34788.92 **
GOOD_EXIT=0
ALL_ASSERTIONS=PASSPASS=1 FAIL=0 证明计数器的复位初值与连续五次递增全部通过;脚本末尾的 ALL_ASSERTIONS=PASS 则同时校验了六组错配的预期结果——两组静默跳过必须不含 TESTS= 行、顶层错配与 Verilator 版本门必须非零退出、--no-timing 必须拒绝延迟而默认编译必须成功。任何一条预期反转,脚本整体失败,避免“修复”只是把某类错误悄悄换成了另一类。
代码获取:计数器 RTL、测试文件、六组 Makefile 与一键复现脚本位于公开仓
experiments/ts-cocotb-toplevel-lang-error/。准备好 Docker 后执行bash run.sh,末行出现ALL_ASSERTIONS=PASS即全部场景符合预期。
实验环境
- 机器/系统:Linux x86_64 Docker 容器 / Ubuntu 24.04.5 LTS(镜像 geek-eda:0.3-sta)
- 工具版本:cocotb 2.1.0、Icarus Verilog 12.0 (stable)、Verilator 5.020 (Debian 5.020-1)、Python 3.12.3
- 日期:2026-11-02
- 完整命令:
bash run.sh这一篇在知识体系里的位置
本篇处理 cocotb 工程“仿真如何被组织起来”的配置面:语言、顶层与仿真器选择。cocotb no tests to run 排查处理 Python 测试模块的发现问题,cocotb plusargs 传参失败处理命令行参数通道,cocotb BinaryValue 迁移处理测试代码内的值对象 API。配置错误大多发生在测试运行之前,且可能静默跳过;测试代码错误则发生在 running 之后,两者排查入口完全不同。
总结
cocotb TOPLEVEL_LANG 配置错误按三个阶段现形:TOPLEVEL_LANG 不是精确小写 verilog 时,Icarus 规则只打印 Skipping simulation 且退出码 0、零测试执行,漏写则在 2.1.0 被源文件自动推断;TOPLEVEL 与 RTL 模块名不符时,iverilog 以 -s 找不到 root module、退出码 2;SIM=verilator 在 cocotb 2.1.0 上要求 Verilator 不低于 5.036,5.020 在 make 解析期即被拒绝,含过程延迟的代码还受 --timing 开关约束。修复后以日志中真实出现 TESTS=1 PASS=1 FAIL=0 而非 make 退出码作为通过判据。