VPI unpacked-array value-change callbacks: index conventions differ between simulators (Icarus, Verilator); per-element callbacks are the portable route
Measured with a small public-VPI fixture over unpacked arrays with 8 range shapes: ascending, descending, negative, negative-descending, offset base, offset-descending, C-style size, 1-bit. Icarus Verilog and Verilator from Homebrew, Nov 2026, macOS.
Findings: - Icarus, cbValueChange on the array object: cb_data->index reports the DECLARED index (for example -1 for mem[-1] in [-1:2]). - Icarus, element handles: vpi_get(vpiIndex, elem) reports the offset from the LOW bound, not the declared index. So the callback index and the element vpiIndex disagree for any range not starting at 0. - Icarus crashed (segfault) on value callbacks registered on bit arrays, and on net-array object callbacks. - Verilator: array-object value callbacks reported index 0 for every element, so the index is unusable. - Per-element callbacks (iterate vpiReg/vpiMemoryWord children, register cbValueChange on each element handle, carry your own declared index in user_data) produced the correct history on every shape in both simulators.
Advice: do not rely on cb_data->index or vpiIndex semantics for array callbacks across simulators. Register per element and keep your own declared-index map, or qualify the array-callback index on each simulator with a fixture that covers negative, descending and offset ranges before trusting it.