iverilog 数组下标越界:memory 整 word 读写为何静默得到 x
更新时间:2026-10-31。本篇处理
reg [7:0] mem [0:3]这类 memory( unpacked array )的整个 word 访问:索引本身越过数组边界,而不是在一个已向量的内部选错 bit。向量 part-select 越界是另一条排查路径,见 iverilog part-select 越界。
本文要回答的问题
- 运行期索引等于上界、为负数或含 X 时,整个 word 会读到什么?
- 越界写会不会覆盖相邻 word,或者让 vvp 报错退出?
- 为什么编译通过、仿真到
$finish,仍不能证明数组访问合法? - 怎样在测试平台中把这类静默错误变成明确失败?
先说答案:在本文的 Icarus Verilog 13.0 实测中,三种非法索引读出的八位值都是 xx;三次越界写都没有改变四个合法 word,也没有产生运行期错误,仿真正常结束。这是本版本、本实验的可复现行为,不应推断为所有模拟器都必然静默,更不能把 xx 当成业务数据继续传播。
一、先分清数组边界与 word 内位边界
声明 reg [7:0] mem [0:3] 包含两层选择。第一层的 mem[index] 从四个 word 中选一个,合法索引只有 0、1、2、3;第二层才是选中八位 word 后访问某一位或某一段。mem[4] 错在第一层,而 mem[0][8] 错在第二层,两者的诊断与运行表现不能混用。
图中蓝色部分才是实际存在的存储对象,红色索引没有可映射的 word。关键是「等于 4」已经越界:范围上界写成 3,并不表示额外包含一个索引 4。负索引和含 X 的索引同样不能选出合法对象,但它们不是被自动截断到 0 或 3。
二、最小复现:只让索引在运行期变化
测试平台先给四个 word 写入 a0 到 a3,索引使用 integer 或三态变量,避免把实验变成单纯的常量选择检查。核心读访问如下:
reg [7:0] mem [0:3];
integer index;
reg [2:0] x_index;
index = 4;
$display("READ index=%0d value=%h", index, mem[index]);
index = -1;
$display("READ index=%0d value=%h", index, mem[index]);
x_index = 3'bxxx;
$display("READ index=x value=%h", mem[x_index]);写访问使用同一组索引,并在每次赋值后打印全部合法 word。这样既能观察是否产生错误,也能检查是否误写首项、末项或相邻项。完整源码见文末公开仓,编译显式打开了 -Wall。
原始输出中,三行 READ 的值均为 xx。每次 WRITE 后,MEM [0] 到 MEM [3] 仍保持初值;最后一行表明执行到了正常 $finish。编译和运行阶段都没有出现针对这些动态越界访问的 warning 或 error,因此不能声称 Icarus 一定会提醒数组边界错误。
--- runtime out-of-bounds reads ---
READ index=4 value=xx
READ index=-1 value=xx
READ index=x value=xx
--- runtime out-of-bounds writes ---
WRITE index=4 value=ff
MEM [0]=a0 [1]=a1 [2]=a2 [3]=a3
WRITE index=-1 value=ee
MEM [0]=a0 [1]=a1 [2]=a2 [3]=a3
WRITE index=x value=dd
MEM [0]=a0 [1]=a1 [2]=a2 [3]=a3
END: simulation reached finish normally这段摘录保留了读和写的完整对照:未知值只出现在读取结果中,三次写操作后的四个合法 word 都是原值。最后的正常结束信息与前面的非法索引同时存在,正是不能拿 $finish 代替数组访问验收的原因。
三、六种访问的结果不能只看退出码
把读和写分开看,才能理解这个坑为什么容易漏过。读越界至少通过未知值留下了痕迹,写越界却可能只留下「数据没有更新」这一功能现象。
| 运行期索引 | 整个 word 读取 | 整个 word 写入后的合法内容 | 本次仿真诊断 |
|---|---|---|---|
| 4,等于声明长度 | xx | 四个 word 均不变 | 无 warning/error |
| -1 | xx | 四个 word 均不变 | 无 warning/error |
| X 索引 | xx | 四个 word 均不变 | 无 warning/error |
| 0 到 3 | 对应已知值 | 只更新指定 word | 正常访问 |
退出码为 0 只说明模拟器完成了这次执行,不说明每次内存访问都有效。尤其在写路径中,程序随后可能重新写入正确数据,表面结果完全正常;也可能因为状态没有更新而在很远的地方失败,真正的错误赋值早已离开波形窗口。
读出的 xx 同样可能被掩盖。四态逻辑里的 X 会参与后续运算与条件判断,若后续测试只检查时钟是否走完、没有检查数据是否已知,仿真结束并不代表验证通过。也不要把 X 索引简单理解为「随机选了一个 word」:本实验读得未知值,三次写入均未改变合法内容。
四、在访问点增加边界与未知值检查
对仿真模型和测试平台,最直接的修复是在访问前拒绝非法索引,而不是访问后再猜 xx 的来源。下面函数同时检查 X/Z 索引和数值边界,调用方只有在检查通过后才访问数组:
function index_ok;
input integer idx;
begin
if ((^idx === 1'bx) || (idx < 0) || (idx > 3)) begin
$display("FAIL: invalid mem index %0d", idx);
index_ok = 1'b0;
end else begin
index_ok = 1'b1;
end
end
endfunction其中归约异或用于发现索引里的未知位,=== 做四态相等比较;上下界检查覆盖负数和索引 4。实际项目可用 $error 或 $fatal 替代打印,让非法访问立即进入回归失败统计。数组深度变化时,边界也应由参数统一生成,避免检查仍写死 3。
| 防护位置 | 推荐做法 | 能发现的问题 |
|---|---|---|
| 驱动索引前 | 断言索引已知且在范围内 | 上游计算错误、未初始化索引 |
| 数组访问前 | 用 guard 包住读写,非法即失败 | 上界、负索引、X 索引 |
| 读取数据后 | 检查数据不含非预期 X | 越界读及其他未知值传播 |
| 回归结束时 | 要求错误计数为 0,而非只看 finish | 静默写失败与被吞掉的错误 |
防护应与测试目的匹配。若测试故意注入非法索引,就断言保护逻辑确实报警;正常功能测试则不应靠捕获异常后继续运行来掩盖设计缺陷。对可综合 RTL,还要确认综合后数组映射与边界策略符合目标器件,不能把模拟器的运行表现当成硬件保证。
五、看到 xx 或写入无效时怎样反查
不要先把所有 xx 都归因于越界。未初始化 word、多个驱动冲突、时序采样过早也可能产生未知值。有效做法是把索引和访问时刻一起打印,再与数组初始化顺序对照:若合法索引也读出 xx,先查初始化;若只有边界或含 X 的索引异常,再沿索引的计算链向上追。本文实验先写满四项,正是为了排除「从未初始化」这个干扰因素。
写路径没有 xx 输出可供搜索时,可以临时在每次赋值前后记录 index、写数据和目标 word。若打印显示执行了赋值而合法 word 没变,应立即检查索引,而不是反复修改位宽。还可以加入一个已知哨兵值:预先给首项和末项写入特殊内容,若越界访问后它们仍不变,只能说明本次实现没有误写这些位置,不能证明这次写操作合法。
索引为 X 时,要继续追它的来源。常见原因包括地址寄存器未复位、位选本身越界、组合逻辑存在未覆盖分支。把 X 索引强制改成 0 虽然可能让未知值消失,却会把错误访问变成对首项的真实写入,反而更难定位。正确顺序是恢复索引的确定性,再检查数值范围,最后复跑数据断言。
对于参数化数组,建议集中定义深度,并让地址宽度、循环上界和运行时检查共用该参数。还要留意深度不是 2 的幂的情况:例如深度为 5 时,三位地址可以表示 5、6、7,位宽足够不代表所有编码都有效。功能回归至少应覆盖首项、末项、上界外一项和负索引;若索引可能含 X,再增加未知索引用例,并验证保护逻辑会明确失败。
实验环境
- 机器/系统:Mac / macOS,Darwin 25.5.0
- 工具版本:Icarus Verilog 13.0 stable(v13_0)
- 日期:2026-10-31
- 完整命令:
bash run.sh;脚本执行iverilog -g2012 -Wall -s tb -o array_word_oob.vvp array_word_oob.v && vvp array_word_oob.vvp
代码获取
最小测试平台与一键复跑脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-iverilog-array-word-oob。安装 iverilog 后进入该目录执行 bash run.sh,即可对照三种索引的读取值、写入后的数组内容和正常结束信息。
这一篇在知识体系里的位置
本篇属于 EDA 排错簇的数组访问节点,处理 iverilog 数组下标越界发生在 memory 整个 word 选择层的情况。若错误是 data[7:0] 内向量位选或 part-select 越界,应转看 iverilog part-select 越界;更多工具错误可从 EDA 排错目录继续定位。
总结
iverilog 数组下标越界访问整个 memory word 时,不能依赖编译器或 vvp 必然报警。Icarus Verilog 13.0 的本次实测中,索引 4、-1 和 X 都读出 xx,对应写入均未改变合法 word,且仿真正常结束。排查时应先区分数组索引与向量位选,在访问前检查未知位和上下界,并让测试回归对非法访问明确失败,而不是只检查是否执行到 $finish。