iverilog $readmemh 数据格式错误:非法字符与越界地址排查
更新时间:2026-10-30。FAQ 速查页,属于 EDA 报错排查系列。iverilog $readmemh 数据格式错误与 iverilog 存储器越界选择不是同一层问题:前者由
vvp读取外部文件时报告,后者通常是 HDL 表达式的选择范围问题。
首屏答案:iverilog 编译成功只说明 HDL 能生成仿真程序,并不检查 $readmemh 数据文件的内容。非法字符会让本次加载停在该词,越界显式地址会报 out of range;本机 Icarus Verilog 13.0 中两种情况的 vvp 退出码都仍为 0,必须同时检查诊断和存储器内容。
本文要回答的问题
Invalid input character指向源文件还是数据文件?- 为什么编译退出 0,运行时报错后
vvp仍退出 0? @08为什么越界,报错前已经读入的数据会不会保留?- 怎样修正数据并验证整个存储器,而不只是让报错消失?
排查顺序应把「编译」「加载」「结果校验」分开,避免用进程退出码替代数据正确性判断。
这张流程图的关键是第二步和第三步不能合并:vvp 打出错误后仍可能继续执行测试平台,只有读出各地址的内容,才能知道加载停在哪里、此前哪些词已经生效。
一、完整报错:iverilog $readmemh 数据格式错误出现在运行期
实验使用 8 项、每项 8 位的存储器 reg [7:0] mem [0:7]。测试平台先把每项置为 xx,再通过 +MEM_FILE=<文件名> 选择数据文件;这样,未成功加载的项不会与合法的 00 混淆。编译命令为 iverilog -g2012 -s tb -o simv tb.sv,运行命令为 vvp simv +MEM_FILE=bad-char.hex。
日志第 4 行的 compile exit=0 与后面的运行时 ERROR 并不矛盾。tb.sv:16 是调用 $readmemh 的 HDL 行,括号中的 bad-char.hex 才是实际输入;Invalid input character: G 指明第三个数据词 G2 中的 G 不属于十六进制字符。
这次非法字符用例只载入了 mem[0]=00 和 mem[1]=11,从 mem[2] 起全部为 xx。越界用例则不同:地址 0 到 7 的八个词均已保留,随后 @08 试图写第九项,才出现 address (0x8) is out of range [0x0:0x7]。两个用例末尾分别是 bad-char exit=0 和 bad-address exit=0,所以 CI 只检查 shell 退出码会把两次失败都判成成功。
二、根因:字符解析和地址边界由 vvp 在加载时判断
$readmemh 是仿真时执行的系统任务,不是编译期文本包含。数据文件名甚至可以通过 plusarg 在运行时才确定,编译器自然无法预先知道 G2 或 @08。无显式地址时,数据词从起始地址顺序写入;@ 后接十六进制地址,会改变下一个词的写入位置。
| 数据写法 | 本机 13.0 行为 | 存储器结果 | 修复方向 |
|---|---|---|---|
第三个词为 G2 | 报非法字符 G,退出 0 | 前两项有效,其余保持 xx | 改为合法词,如 22 |
八项存储器中写 @08 | 报地址 8 超出 [0:7],退出 0 | 地址 0 到 7 已加载 | 删除该词或扩大目标存储器 |
@00 后写八个合法词 | 无格式诊断,退出 0 | 八项全部加载 | 再做数值断言确认业务含义 |
越界地址不一定意味着数据文件多了一行。也可能是存储器声明写成 [0:7],生成数据的脚本却按 1 到 8 编号;或者数据文件本来面向 16 项存储器,却被复用到 8 项实例。修复前要确认哪一边是接口契约,不能为了消错直接把存储器无限扩大。
红色表示运行期诊断入口,蓝色是定位动作,橙色对应两类数据修复,绿色才是完整复验。两条修复路径不能互换:把 G2 改成 22 不会解决地址 8 越界,扩大存储器也不会让非法字符变得可解析。
三、排查清单:先定位数据词,再检查加载后的值
拿到报错后按下列顺序核对:
- 读完整括号内容:确认出错的是哪个数据文件;同名文件可能因当前工作目录不同而读到另一份。
- 检查词法而非只看扩展名:
.hex文件仍可能混入0x22、逗号、中文标点或 CSV 表头;本例非法字符是G,应使用0-9、a-f或A-F。 - 换算显式地址:
@08是十六进制地址 8,而[0:7]最后一项是 7;不要把文件行号当成存储器地址。 - 观察部分加载结果:出现
xx说明该项没被合法值覆盖;没有xx也不能证明后半段语义正确。 - 检查脚本退出判定:把
vvp的 0 与数据断言分开处理,回归测试必须让坏数据真正失败。
如果数据文件由脚本导出,还应在生成端核对记录数与目标深度,并保留地址和值的映射。运行时日志只能告诉你第一个被拒绝的位置,不能保证此前写入的词都符合预期;尤其是格式错误发生在文件末尾时,表面完整的打印结果仍可能掩盖错位数据。
测试平台中预置未知值、逐项打印,是定位部分加载最直接的办法:
for (i = 0; i < 8; i = i + 1)
mem[i] = 8'hxx;
$readmemh(mem_file, mem);
for (i = 0; i < 8; i = i + 1)
$display("mem[%0d]=%02h", i, mem[i]);正式回归还应在加载后增加断言,例如对每项使用 (^mem[i] === 1'bx) 检测未知位,并用 $fatal(1, ...) 让进程非零退出。具体期望值应按设计契约检查;只判断「不是 x」能抓住漏加载,却不能发现把 22 写成 23 的合法但错误数据。
四、修复复跑:同一存储器、同一命令验证八项数据
本例的目标就是 8 项存储器,因此修复不是扩大数组,而是把 G2 改为 22,删除越过最后一项的 @08 88。修复后的完整数据只有一行,显式从地址 0 开始:
@00 00 11 22 33 44 55 66 77复跑仍执行 vvp simv +MEM_FILE=fixed.hex,没有增加屏蔽诊断的选项。脚本另外检查 mem[2]=22,并保留最终 PASS 行。
=== fixed ===
$ vvp simv +MEM_FILE=fixed.hex
mem[0]=00
mem[1]=11
mem[2]=22
mem[3]=33
mem[4]=44
mem[5]=55
mem[6]=66
mem[7]=77
tb.sv:19: $finish called at 0 (1ps)
fixed exit=0
PASS: invalid character and out-of-range address diagnosed; fixed data loaded这次没有 ERROR,八项从 00 到 77 全部符合数据文件,fixed exit=0 才有意义。注意 PASS 不是对「退出 0」的重复描述,而是建立在输出值核对之上;实际工程应把这一步换成对所有关键地址的自动断言。
实验环境
- 机器/系统:macOS 26.5.2,x86_64。
- 工具版本:Icarus Verilog 13.0 (stable) (v13_0)。
- 日期:2026-10-30。
- 完整命令:
bash run.sh;脚本以iverilog -g2012 -s tb -o simv tb.sv编译,再分别执行vvp simv +MEM_FILE=bad-char.hex、vvp simv +MEM_FILE=bad-address.hex、vvp simv +MEM_FILE=fixed.hex,标准输出与标准错误合并记录,脚本最终退出 0。
代码获取
测试平台、三份数据、复跑脚本和完整记录位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-iverilog-readmemh-format-error。进入目录执行 bash run.sh,即可复现两类运行期错误并验证修复数据。
这一篇在知识体系里的位置
本篇解决外部初始化文件进入存储器时的格式与地址问题。若数据能加载但位宽转换后的数值不符合预期,可继续看 iverilog 数值截断排查;若仿真已运行却没有波形,则转查 iverilog VCD dumpvars 缺失。返回 EDA 报错排查系列 可按编译期与运行期症状继续分诊。
总结
iverilog $readmemh 数据格式错误要沿数据文件和运行期加载结果排查:G 这类非法字符会中断解析,超出 [0:7] 的 @08 会触发地址越界,而本机两种情况下 vvp 都返回 0。修正数据后必须用原命令复跑并逐项核对存储器;在自动化回归中增加数值或未知位断言,才能避免「有报错但构建仍成功」。