Fix #1134: Allow struct member access in unpacked array of packed structs

When accessing a member of a packed struct within an unpacked array
(e.g., `tests[0].a` where `tests` is `test_t tests[0:1]`), the
assertion `base_index.size()+1 == net->packed_dimensions()` would fail
because unpacked indices were incorrectly included in base_index.

The fix:
1. Separate unpacked indices from packed indices before calling
   check_for_struct_members()
2. Compute the word index for unpacked array access using
   normalize_variable_unpacked()
3. Pass the word index to check_for_struct_members(), which now
   creates NetESignal with the proper word selector

This allows expressions like `array_of_structs[i].member` to work
correctly, selecting the right array element before extracting the
packed struct member.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andrew Pullin 2026-01-23 14:54:06 -08:00
parent 54dfd0a702
commit e00e89d8f8
3 changed files with 87 additions and 10 deletions

View File

@ -2389,12 +2389,17 @@ bool calculate_part(const LineInfo*li, Design*des, NetScope*scope,
* the net is a struct. If that turns out to be the case, and the
* struct is packed, then return a NetExpr that selects the member out
* of the variable.
*
* The word_index parameter is used when the struct is in an unpacked
* array - it selects which array word to access before the packed
* member selection.
*/
static NetExpr* check_for_struct_members(const LineInfo*li,
Design*des, NetScope*scope,
NetNet*net,
const list<index_component_t>&base_index,
pform_name_t member_path)
pform_name_t member_path,
NetExpr*word_index = 0)
{
const netstruct_t*struct_type = net->struct_type();
ivl_assert(*li, struct_type);
@ -2707,7 +2712,7 @@ static NetExpr* check_for_struct_members(const LineInfo*li,
packed_base = 0;
}
NetESignal*sig = new NetESignal(net);
NetESignal*sig = new NetESignal(net, word_index);
NetExpr *base = packed_base? packed_base : make_const_val(off);
NetESelect*sel = new NetESelect(sig, base, use_width, member_type);
@ -2715,13 +2720,59 @@ static NetExpr* check_for_struct_members(const LineInfo*li,
cerr << li->get_fileline() << ": check_for_struct_member: "
<< "Finally, completed_path=" << completed_path
<< ", off=" << off << ", use_width=" << use_width
<< ", base=" << *base
<< endl;
<< ", base=" << *base;
if (word_index)
cerr << ", word_index=" << *word_index;
cerr << endl;
}
return sel;
}
/*
* Helper function to elaborate struct member access in an unpacked array.
* This handles separating unpacked indices from packed indices,
* computing the word index, and calling check_for_struct_members.
*/
static NetExpr* elaborate_struct_member_access(const PExpr*expr,
Design*des, NetScope*scope,
NetNet*net,
const pform_name_t&path_head,
const pform_name_t&path_tail)
{
// Separate unpacked indices from packed indices.
// check_for_struct_members expects only packed indices.
list<index_component_t> all_index = path_head.back().index;
list<index_component_t> unpacked_index;
for (unsigned idx = 0 ; idx < net->unpacked_dimensions() ; idx += 1) {
unpacked_index.push_back(all_index.front());
all_index.pop_front();
}
list<index_component_t>& packed_index = all_index;
// Compute word index for unpacked array access.
NetExpr*word_index = 0;
if (net->unpacked_dimensions() > 0) {
list<NetExpr*>unpacked_indices_expr;
list<long> unpacked_indices_const;
indices_flags idx_flags;
indices_to_expressions(des, scope, expr,
unpacked_index, net->unpacked_dimensions(),
false, idx_flags,
unpacked_indices_expr, unpacked_indices_const);
if (idx_flags.variable) {
word_index = normalize_variable_unpacked(net, unpacked_indices_expr);
} else if (!idx_flags.invalid && !idx_flags.undefined) {
word_index = normalize_variable_unpacked(net, unpacked_indices_const);
}
}
return check_for_struct_members(expr, des, scope, net,
packed_index,
path_tail, word_index);
}
static NetExpr* class_static_property_expression(const LineInfo*li,
const netclass_t*class_type,
perm_string name)
@ -4555,9 +4606,8 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
if (!sr.path_tail.empty()) {
if (net->struct_type()) {
return check_for_struct_members(this, des, scope, net,
sr.path_head.back().index,
sr.path_tail);
return elaborate_struct_member_access(this, des, scope, net,
sr.path_head, sr.path_tail);
} else if (dynamic_cast<const netclass_t*>(sr.type)) {
return elaborate_expr_class_field_(des, scope, sr, 0, flags);
}
@ -4782,9 +4832,8 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope,
<< endl;
}
return check_for_struct_members(this, des, scope, sr.net,
sr.path_head.back().index,
sr.path_tail);
return elaborate_struct_member_access(this, des, scope, sr.net,
sr.path_head, sr.path_tail);
}
// If this is an array object, and there are members in

View File

@ -0,0 +1,27 @@
// Test for GitHub issue #1134
// Accessing member of packed struct in unpacked array should work
typedef struct packed {
logic a, b;
} test_t;
module test;
// tests[0] = {a:0, b:1}, tests[1] = {a:1, b:0}
test_t tests [0:1] = '{'{'b0, 'b1}, '{'b1, 'b0}};
wire w0, w1;
assign w0 = tests[0].a; // Should be 0
assign w1 = tests[1].a; // Should be 1
initial begin
#1;
if (w0 !== 1'b0) begin
$display("FAILED: tests[0].a = %b, expected 0", w0);
$finish;
end
if (w1 !== 1'b1) begin
$display("FAILED: tests[1].a = %b, expected 1", w1);
$finish;
end
$display("PASSED");
end
endmodule

View File

@ -992,5 +992,6 @@ ipsupsel_real_idx CE,-g2012 ivltests gold=ipsupsel_real_idx.gold
real_edges CE,-g2012 ivltests gold=real_edges.gold
br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold
br_gh670 normal,-g2009 ivltests
br_gh1134 normal,-g2012 ivltests
br_gh1267 normal,-g2012 ivltests
br_gh1268 normal,-g2012 ivltests