iverilog $readmemh 文件找不到:相对路径与格式错误排查
iverilog $readmemh 文件找不到 的典型现场是:编译已经生成 .vvp,运行时才出现 Unable to open ... for reading。这类问题要同时看数据文件、启动目录和存储器内容;它不同于 iverilog $readmemh 文件找不到之外的语法错误排查,因为源码可以通过编译,故障到仿真阶段才暴露。
本文要回答的问题
$readmemh的相对路径究竟以源码目录、可执行文件目录还是当前工作目录为基准?- 文件不存在、启动目录错误和 hex 内容非法,诊断信息有什么差别?
- 为什么只看
iverilog的退出码会漏掉问题,怎样用存储器自检补足? - 怎样修路径,并用同一个
.vvp的对照运行确认修复有效?
这张漏斗把问题拆成两道门:Unable to open 说明还没读到内容,应查路径;Invalid input character 说明文件已经打开,应查格式。两类错误都可能让后续自检失败,但修复对象不同。
一、完整报错:编译退出 0,仿真退出 1
缺文件用例只有 19 行,调用 $readmemh("data/not-present.hex", mem) 后检查四项数据。使用 Icarus Verilog 13.0,编译与运行命令为:
iverilog -g2012 -s tb_readmemh -o build/broken.vvp broken/tb_readmemh.sv
vvp build/broken.vvp合并标准输出和标准错误后,关键原文如下。ERROR 指向系统任务所在第 7 行,并原样回显未能打开的路径;随后 FATAL 是测试平台发现四个单元仍为 xx 后主动退出,不是 $readmemh 自己打印的第二份错误。
broken compile exit=0 (expected=0)
ERROR: broken/tb_readmemh.sv:7: $readmemh: Unable to open data/not-present.hex for reading.
FATAL: broken/tb_readmemh.sv:13: FAIL: memory was not initialized: xx xx xx xx
Time: 1000 Scope: tb_readmemh
broken simulation exit=1 (expected=1)因此排查要分别记录 iverilog 与 vvp 的退出码。本例编译退出 0 只证明语法和 elaboration 通过;加载数据属于运行期动作。若没有后面的存储器检查,某些仿真可能带着未知值继续跑,直到更远处才出现难以解释的功能错误。
二、根因:相对路径以 vvp 的当前工作目录为准
当前工作目录(current working directory,cwd)是进程启动时所在的目录。这里的 data/pattern.hex 不含盘符或根路径,vvp 会把它拼接到自己的 cwd 后面;它不会自动改为相对 .sv 源文件,也不会相对 .vvp 文件所在位置搜索。
修复版从实验根目录启动时,根目录下确有 data/pattern.hex,加载成功。随后不改源码、不重编译,只进入 fixed/ 再运行同一个 ../build/fixed.vvp,解析目标就变成 fixed/data/pattern.hex,该文件不存在,于是重新出现 Unable to open。这个对照直接排除了“路径会跟着源码走”的猜测。
| 运行方式 | 相对路径实际落点 | 本例结果 |
|---|---|---|
在实验根目录运行 vvp build/fixed.vvp | 实验根目录 /data/pattern.hex | 四项数据正确,退出 0 |
在 fixed/ 运行 vvp ../build/fixed.vvp | fixed/data/pattern.hex | 打不开文件,自检退出 1 |
编译器读取 fixed/tb_readmemh.sv | 只决定源码如何找到 | 不改变运行期数据文件基准 |
绝对路径能临时消除歧义,但把个人目录写进测试平台后,换机器或持续集成环境就会失效。更稳妥的工程做法是统一仿真入口:脚本先 cd 到约定目录,再启动 vvp;或者由构建系统传入已确定的数据路径,并让所有调用方使用同一约定。
三、排查清单:先分缺失与格式,再检查启动方式
下面的路径把红色用于故障入口,蓝色用于核对动作,橙色用于路径修复,绿色表示正确加载,紫色表示需要固化的工程约束。注意不要在看到任何 $readmemh 错误时都直接改目录。
实际检查可按四步执行:从报错复制文件名,不手打一遍;在准备运行 vvp 的目录执行 pwd,确认 cwd;用 ls -l data/pattern.hex 检查该目录下的目标是否存在且可读;文件能打开但仍失败时,再检查 hex 字符、位宽和地址范围。若由 Makefile、IDE 或 CI 启动,还应查看它是否先切换了目录,不能拿交互终端的 cwd 代替仿真进程的 cwd。
格式错误是另一条分支。本例文件确实存在,但内容为 11 22 GG 44,其中 G 不是十六进制数字。真实输出明确指出文件名和非法字符,前两项已加载,后两项仍为未知值:
malformed compile exit=0 (expected=0)
ERROR: malformed/tb_readmemh.sv:7: $readmemh(data/malformed.hex): Invalid input character: G
FATAL: malformed/tb_readmemh.sv:13: FAIL: memory was not initialized: 11 22 xx xx
Time: 1000 Scope: tb_readmemh
malformed simulation exit=1 (expected=1)这时把文件复制到别的目录没有作用。应修正 GG 为合法数据,并检查每项是否超过存储单元位宽;若使用 @地址,还要确认地址没有越过声明范围。文件缺失的诊断没有读取进度,而这里的 11 22 已写入,正好说明文件访问已经成功。
四、修复代码:保留相对路径,同时固定启动目录
故障版本与修复版本只差数据文件名。修复版使用实际存在的 data/pattern.hex:
initial begin
$readmemh("data/pattern.hex", mem);
#1;
if (mem[0] === 8'h11 && mem[1] === 8'h22 &&
mem[2] === 8'h33 && mem[3] === 8'h44) begin
$display("PASS: memory initialized from hex file");
end else begin
$fatal(1, "FAIL: memory was not initialized: %h %h %h %h",
mem[0], mem[1], mem[2], mem[3]);
end
$finish;
end四态相等运算符 === 会把未知值也纳入比较,因此路径失败不会被当成普通的不相等悄悄放过。自检不是 $readmemh 的必需语法,却能给批处理一个明确验收信号。配套脚本先定位自身目录并 cd,随后所有用例都从该入口运行;错误 cwd 对照则显式进入 fixed/,仅用于证明路径语义。
五、复跑 PASS:同一个文件只在正确 cwd 下成功
下面的运行记录覆盖缺文件、正确路径和错误 cwd 三段,包含诊断与各自退出码。重点看修复版两次运行的差别:仿真文件完全相同,只有启动目录改变。
正确路径组打印 PASS: memory initialized from hex file,vvp 退出 0;错误 cwd 组重新打印 Unable to open data/pattern.hex,自检退出 1。非法 hex 组也按预期失败。复现脚本逐项核对实际退出码,最后打印总 PASS 并退出 0,这个总 PASS 表示三组故障均被捕获且修复组通过,不表示故障用例本身成功。
实验环境
- 机器/系统:Intel x86_64 Mac / macOS 26.5.2。
- 工具版本:Icarus Verilog 13.0 (stable) (v13_0),
iverilog与vvp同版本。 - 日期:2026-10-30。
- 完整命令:在实验目录运行
bash run.sh;脚本执行四次iverilog编译,并分别运行缺文件、正确 cwd、错误 cwd 和非法 hex 四个仿真,所有输出均合并标准输出与标准错误。
代码获取
三组测试平台、正常与非法 hex 数据、复现脚本及原始运行记录见公开仓 experiments/ts-iverilog-readmemh-file-missing。进入该目录后运行 bash run.sh 即可复现全部退出码。
这一篇在知识体系里的位置
本篇属于 iverilog $readmemh 文件找不到所在的 EDA 报错排查系列,聚焦运行期数据加载。若错误发生在编译期,并指向隐式网线而非数据文件,可继续阅读 Icarus Verilog implicit wire definition 排查;两者的共同方法都是保留完整诊断、分别记录阶段退出码,再做最小对照。
总结
iverilog $readmemh 文件找不到 时,相对路径以 vvp 进程的当前工作目录为基准,而不是源码或 .vvp 所在目录。Unable to open 应查 cwd、文件存在性和权限,Invalid input character 则应修 hex 内容。实测中正确路径退出 0,同一 .vvp 换到错误目录后退出 1;固定仿真入口并用存储器自检,才能避免把未初始化的未知值带进后续仿真。