cocotb 随机种子回归流程:随机激励、失败复现与三种子扫描
定向用例只能覆盖你想得到的情况,随机激励的价值在于撞你想不到的输入;但「随机」一旦失败就必须能原样重放,否则不可调试。本文走通 cocotb 2.1.0 的随机种子回归流程:给一个 8 位饱和加法器喂随机事务,看 cocotb 怎样用种子控制整条随机流,失败后从哪里找回种子、怎样同种子复现,最后用一个三十行的 shell 脚本扫三粒种子做回归汇总。第一个 cocotb 工程的骨架可以先看 cocotb 最小测试平台 Python 模板,纯 SystemVerilog 侧的种子循环写法对照 仿真回归控制 Shell 脚本模板。
本文要回答的问题
- cocotb 2.1 里随机种子通过哪个变量给,旧教程的
RANDOM_SEED和+seed还能不能用? - 一次随机测试失败了,种子号从哪里找回,怎样保证重放逐位相同?
- 「同一种子」在 cocotb 里是怎么映射到每个测试用例的?
- 怎样用 shell 循环扫三粒种子并汇总通过与失败,让 CI 直接判红绿?
一、随机种子回归在验证流程里的位置
种子回归接在「定向用例已经全过」之后:上游是可编译的 RTL 和一个带参考模型的 cocotb 测试,本步反复重跑同一测试、每次换一粒种子,产出的是逐种子日志和一份通过/失败汇总;失败的种子作为现场凭证回流给调试,修复后再用同一清单回归。
读这张图抓住一条闭环:蓝色定种子决定本轮全部随机性,橙色跑随机产生判定,红色留现场保证失败可追溯,绿色重放修复把随机问题变成确定问题。第四步不是终点——修复后要回到第一步用同一批种子再扫一遍。
这张图里最容易被忽略的是第二格:cocotb 并不是直接拿回归种子播种,而是把回归种子与测试全名的 SHA-1 摘要相加,派生出每用例种子。所以同一轮里多个用例各有独立随机流,而用例增删或改名会改变派生值,复现老版本的失败必须连代码版本一起对齐。
二、种子怎么给:命令与环境变量逐段解读
cocotb 2.1.0 的现行通道是环境变量 COCOTB_RANDOM_SEED;走 Makefile 时直接写成 make 变量,Makefile.inc 会把它注入仿真进程环境,效果相同:
make sim COCOTB_RANDOM_SEED=2 # 推荐:make 变量
COCOTB_RANDOM_SEED=2 make sim # 等价:直接给环境变量不给种子时,cocotb 用 int(time.time()) 当种子并在启动日志打印 Seeding Python random module with <数字>——所以即使随机跑,现场也不会丢。旧通道在 2.1 仍然受理但各带一条告警:环境变量 RANDOM_SEED 触发 DeprecationWarning;仿真器 plusarg +seed= 与 +ntb_random_seed=(经 COCOTB_PLUSARGS 传入)触发 FutureWarning,明确警告将来不再给 Python 随机流播种。
| 给法 | cocotb 2.1 行为 | 适用场景 |
|---|---|---|
COCOTB_RANDOM_SEED=2 make sim | 现行通道,无告警 | 复现、回归清单 |
| 不设置 | 按时间取种,日志打印实际种子 | 覆盖率冲刺的随手一跑 |
RANDOM_SEED=2 make sim | 可用,DeprecationWarning | 只用于兼容老脚本 |
COCOTB_PLUSARGS="+seed=2" | 可用,FutureWarning | 与 SV 侧 plusarg 约定对齐时 |
plusarg 通道的传参机制(它和 SIM_ARGS 的区别)在 cocotb plusargs 传参失败里有实测对照。另外两个配套变量顺带记牢:测试模块用 COCOTB_TEST_MODULES 指定,按名字只跑某一条用例用 COCOTB_TEST_FILTER(旧名 MODULE、TESTCASE 均已废弃)——回归里把某个失败用例单独拎出来重放时就要加过滤器。
三、输入输出文件长什么样
工程只有五件输入:被测 RTL sat_add8.v、故意保留的边界 bug 版本 sat_add8_buggy.v、测试 test_sat_add.py、Makefile、回归脚本 run_seeds.sh。Makefile 用 DUT_FILE 在两个 RTL 版本间切换,回归种子作为 make 变量传入:
SIM ?= icarus
TOPLEVEL_LANG ?= verilog
DUT_FILE ?= sat_add8.v
VERILOG_SOURCES += $(PWD)/$(DUT_FILE)
TOPLEVEL := sat_add8
COCOTB_TEST_MODULES := test_sat_add
include $(shell cocotb-config --makefiles)/Makefile.sim测试对组合逻辑驱动 300 笔随机事务,参考模型就是一行 Python:期望值 min(a+b, 255)。用的是标准库 random 的全局生成器——它正是 cocotb 每用例重新播种的对象,自建 random.Random() 实例或 numpy 生成器不受种子管控,随机就不再可复现。
a = random.getrandbits(8)
b = random.getrandbits(8)
dut.a.value = a
dut.b.value = b
await Timer(1, unit="ns") # 等组合输出稳定
assert int(dut.sum.value) == min(a + b, 255)回归脚本为每粒种子做三件事:make clean 清掉上一轮的 sim_build 和 results.xml,带 COCOTB_RANDOM_SEED 跑仿真并把完整输出存进 logs/seed_<种子>.log,再以「退出码为 0 且日志含 TESTS=1 PASS=1 FAIL=0」双重条件判定。注意全程不用 set -e:失败种子不能打断循环,否则后面种子的结果和失败汇总都拿不到。
for s in $SEEDS; do
log="$LOG_DIR/seed_${s}.log"
make clean >/dev/null 2>&1 || true
make sim DUT_FILE="$DUT_FILE" COCOTB_RANDOM_SEED="$s" >"$log" 2>&1
rc=$?
if [ "$rc" -eq 0 ] && grep -q "TESTS=1 PASS=1 FAIL=0" "$log"; then
echo "[REGRESS] seed=$s PASS"
else
echo "[REGRESS] seed=$s FAIL (rc=$rc, log=$log)"
fi
done产物侧除了逐种子日志,还有 results.xml:用例失败时 cocotb 会在其中盖一行 Test failed with COCOTB_RANDOM_SEED=<种子>,这是 CI 归档里最可靠的找种入口。
四、常见错与检查点
第一,随机失败「在我机器上不复现」。九成是没记录种子:先翻失败轮次的日志找 Seeding Python random module with 行,或在 results.xml 里搜 COCOTB_RANDOM_SEED,拿到种子号原样重放。检查点是每轮随机跑都要能回答「这轮种子是几」。
第二,把 plusarg 当主传种通道。+seed 在 2.1 会打印 FutureWarning,未来版本将不再播种 Python 随机流;新脚本一律用 COCOTB_RANDOM_SEED,plusarg 只留给需要和 HDL 侧约定共享的场合。
第三,三粒种子全过就宣布没 bug。本文的 bug 是判空 off-by-one:total > 256 应为 total > 255,只有 a+b 恰好等于 256 时才暴露,255 种输入组合、命中率约 0.39%,300 笔事务下单粒种子理论命中率约 69%。这意味着单粒种子漏检概率约三成——边界条件必须再配定向用例,种子数也要随回归档位增加,节奏参考 仿真回归多久跑一次合理。
第四,改了测试名后旧种子对不上。每用例种子含测试全名摘要,重命名用例或改模块名后同一回归种子派生出的随机流不同;复现历史失败时连测试代码版本一起对齐。
五、真跑:三粒种子抓 bug、同种子复现、修复回归
bug 版 RTL 先做侦察:连续扫 20 粒种子,13 粒抓到失配、7 粒漏检——漏检不是通过,只是这轮随机没撞到边界。从中选 1、2、7 三粒(漏检、抓到、漏检)做正式回归,脚本输出如下。
终端卡里 seed 1 与 seed 7 都报 PASS,seed 2 失败(make 退出码 2、1 个失配),脚本仍把三粒跑完并以 FAILED SEEDS: 2、退出码 1 收口。下面两次单独重放 seed 2,失配都发生在第 239 笔事务、操作数与读数完全相同(a=142、b=114,和为 256,buggy RTL 回绕成 0,期望钳位到 255)——这就是「同种子逐位复现」。
种子号在三处留痕,都在本次日志里:
Seeding Python random module with 2
per-test cocotb.RANDOM_SEED = 1212437837890036762771721612760607300118971206777
Test failed with COCOTB_RANDOM_SEED=2第一行是回归种子,第二行是它与测试名派生后的每用例种子(约 48 位整数),第三行是 results.xml 给失败盖的章。旧通道的告警也是真实打印:RANDOM_SEED 给 11 触发 DeprecationWarning,COCOTB_PLUSARGS="+seed=11" 触发 FutureWarning,但两者本轮仍都打印 Seeding Python random module with 11。
把判空改成 total > 9'd255 后,用同一批种子回归,seed 2 的 300 笔事务失配数归零:
[REGRESS] seed=1 PASS
[REGRESS] seed=2 PASS
[REGRESS] seed=7 PASS
[REGRESS] SUMMARY pass=3 fail=0
[REGRESS] ALL PASS
applied 300 random transactions, mismatches=0修复轮三粒全过、退出码 0。注意这只能证明「这三粒种子 + 既有定向用例下正确」,不能证明 bug 不存在——随机回归是持续叠加的过程,下一轮还会换新种子。
实验环境
- 机器/系统:Docker 容器 geek-eda:0.3-sta,Ubuntu 24.04
- 工具版本:cocotb 2.1.0(/opt/venvs/cocotb 虚拟环境)、Icarus Verilog 12.0、Python 3.12
- 日期:2026-11-03
- 完整命令:
SEEDS="1 2 7" DUT_FILE=sat_add8_buggy.v bash run_seeds.sh;make sim DUT_FILE=sat_add8_buggy.v COCOTB_RANDOM_SEED=2(复现两次);PYTHONWARNINGS=default RANDOM_SEED=11 make sim DUT_FILE=sat_add8.v;make sim DUT_FILE=sat_add8.v COCOTB_PLUSARGS="+seed=11";SEEDS="1 2 7" DUT_FILE=sat_add8.v bash run_seeds.sh(容器内均先source /opt/venvs/cocotb/bin/activate)
代码获取
完整 RTL(含 buggy 版本)、cocotb 测试、Makefile 与种子回归脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/flow-cocotb-random-seed。进入目录后用 docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta bash -lc 'source /opt/venvs/cocotb/bin/activate && SEEDS="1 2 7" DUT_FILE=sat_add8_buggy.v bash run_seeds.sh' 即可复现「2 过 1 挂」;把 DUT_FILE 换成 sat_add8.v 则三粒全过。
这一篇在知识体系里的位置
本篇管「cocotb 随机激励怎样靠种子变成可复现的工程流程」。往测试平台骨架走是 cocotb 最小测试平台 Python 模板,往 shell 回归的通用写法走是 仿真回归控制 Shell 脚本模板;传参通道的细节报错看 cocotb plusargs 传参失败,回归档位规划看 仿真回归多久跑一次合理。
总结
cocotb 随机种子回归流程的核心是一条闭环:用 COCOTB_RANDOM_SEED 定种子(2.1 现行通道,旧 RANDOM_SEED 与 +seed 各带告警),cocotb 把回归种子与测试名派生成每用例种子播种标准库 random,失败时从启动日志或 results.xml 找回种子、同种子逐位重放,再由 shell 脚本逐种子独立日志、失败不中断、末尾汇总并给出非零退出码。容器内实跑证据:buggy 判空在 20 粒种子侦察中被 13 粒抓到,正式回归 1/7 漏检、seed 2 在第 239 笔事务抓到 a+b=256 回绕为 0,两次重放完全一致;修复判空后同一批三粒种子 300 笔事务全部通过。