Skip to content

Read a packed element selected inside an unpacked array's element in compiled code (§7.4.1, §7.4.5, §11.5.1, #985) - #986

Open
chuanseng-ng wants to merge 5 commits into
aionhw:mainfrom
chuanseng-ng:fix/packed-subelem-read
Open

chuanseng-ng wants to merge 5 commits into
aionhw:mainfrom
chuanseng-ng:fix/packed-subelem-read

Conversation

@chuanseng-ng

@chuanseng-ng chuanseng-ng commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #985.

Problem

A packed element selected inside an unpacked array's element read wrong in compiled code (an always block, a continuous assignment): d[1][0] on logic [1:0][7:0] d [2] read 0X instead of the low byte of d[1], and a[1][1][1] on logic [1:0][7:0] a [2][2] read one bit of the element (§7.4.1, §7.4.5, §11.5.1). Writes, initial reads and $display were right.

The same holds when the element is an array of packed structs: b[0][1] on le_t [1:0] b [2], with le_t a 40-bit packed struct, is one struct (§7.4.1, §7.2.1), but compiled code read 0, one bit, or a slice that leaves out the struct's own width.

Cause

In the Index read of the bytecode compiler, the chained packed select (chained_packed_slice, emit_chained_packed_slice_dyn) walks the root's packed dimensions only, so d[1][0] was taken as packed [1][0] of the array's root. A chain longer than the packed dimensions (a[1][1][1]) fell through to the plain bit select of the loaded element.

For struct elements, packed_full_dims lists only the packed array dimensions, not the struct's width. When no dimensions are recorded at all (a single [N-1:0] range, or any range of a typedef-named struct type), the chain was not recognised as selecting into the element.

Fix

  • unpacked_elem_select_depth finds how many indices address the root's unpacked dimensions: 1 for a one-dimensional array (queues and dynamic arrays included), 2 for a two-dimensional one, the constant prefix that names a stored element for a deeper one. It answers only when the remaining indices select into a multi-dimensional packed element.
  • elem_packed_dims gives that element's packed dimensions down to single bits. When the element is wider than the product of the recorded packed dimensions (a packed struct), the rest is one more dimension of that width. When none are recorded, the packed element width gives the shape, but not whether the range is [N-1:0], [0:N-1] or offset. Such a read declines to the AST path, which reads the labels from the declaration, instead of placing the select by guess.
  • compile_unpacked_elem_packed_select reads that element through its existing path and slices the packed element out of it: RangeSelectConst for constant indices (an out-of-range index reads x, §11.5.1), a run-time offset otherwise. An element that does not compile keeps the read on the AST path.
  • The packed chain helpers decline an unpacked root (is_unpacked_chain_root, the same test chained_packed_select_width already applies).
  • The constant and run-time offset arithmetic moves into packed_chain_offset / emit_packed_chain_offset / emit_dyn_slice, shared by both paths; the packed chain's own behaviour is unchanged.

Tests

tests/types/unpacked_elem_packed_select_read.rs, 10 tests, every expected value from the reference simulator.

  • Before the fix, 6 of the first 7 fail. selects_that_already_work (a bit select of an unpacked vector element, a packed element select, a whole element read, a part-select of a packed vector) passes as a guard.
  • queue_dynamic_array_and_typedef_elements was added after the fix. Its source on xezim 2e4e7f99 gives r1=0X r2=0X r3=0X; the reference simulator gives r1=12 r2=78 r3=9a.
  • packed_struct_elements (package, local and inline struct types, typedef-named ranges, ascending and offset ranges, dynamic indices, member and part-selects of a selected struct) fails on the branch before the struct commits.
  • struct_elements_with_recorded_ranges runs its reads in an always block with recorded ranges ([0:1], [2:1], [1:0][1:0]), so they compile to LoadArr + RangeSelectConst rather than taking the AST path.
Probe the reference simulator c35707e6 this branch
reproducer (continuous and always) cont d=ab00000001 a=ab00000001 proc d=ab00000001 a=ab00000001 cont d=ab00000001 a=0000000000 proc d=000000000X a=0000000000 (on 2e4e7f99) matches
1-D / 2-D / 3-D, dynamic, ascending, offset, deeper, part-select, read-back r1=12 r2=78 r3=56 r4=9a r5=de r6=21 r7=87 r8=34 r9=7 r10=1 w1=ee w2=cc r1=0X r2=0X r3=00 r4=00 r5=0X r6=0X r7=0x r8=0X r9=X r10=x w1=0X w2=0X (on 2e4e7f99) matches
queue, dynamic array, typedef element r1=12 r2=78 r3=9a r1=0X r2=0X r3=0X (on 2e4e7f99) matches
packed struct elements r1=dead0000a5 r2=1122334455 r3=abcdef0123 r4=a0b0c0d0e0 r5=789abc r6=a r7=1 r8=a5 r9=2222222222 eq=1 r1=0000000000 r2=0000000001 r3=0000000000 r4=0000000000 r5=000000 r6=x r7=1 r8=a5 r9=0000000001 eq=0 matches
struct elements, recorded ranges r1=abcdef0123 r2=4567890abc r3=1111111111 r4=010203040506 r5=0708a9 r6=2 eq=1 r1=abcdef0123 r2=4567890abc r3=1111111111 r4=00000000000X r5=00000X r6=2 eq=1 matches
  • Full suite (c35707e6 + this branch, pinned core 2ff85ad): 6620 passed, 0 failed, 1 ignored.
  • XEZIM_JIT=1 (--features jit): the 10 tests pass on a --features jit build.
  • perf is not available on this machine (perf_event_paranoid=4), so no instruction counts. The change is in the compiler, not the VM: compile time adds a table lookup on Index reads with a chain, and the emitted code is a load of the element and one slice.

Caches and notes

  • METHOD_CACHE_COMPILER_SALT goes to 0x6370_6666_306c with a comment line: these reads now compile to different instructions, and an element that does not compile now declines.
  • The Note … commit adds the NOTES.md entry under ### Unreleased.

Related: Default, Row and 6 in #879 are stores and are not changed here. The packed dimensions of a typedef-named struct array (#984) are not recorded by the elaborator; until they are, reads into such arrays with more than one packed select stay on the AST path, which reads them right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant