mirror of
https://github.com/YosysHQ/yosys
synced 2026-03-10 07:10:31 +00:00
Merge pull request #5630 from apullin/array-assignment
ast: Add support for array-to-array assignment
This commit is contained in:
commit
629bf3dffd
2 changed files with 281 additions and 0 deletions
|
|
@ -269,6 +269,83 @@ static int add_dimension(AstNode *node, AstNode *rnode)
|
|||
node->input_error("Unpacked array in packed struct/union member %s\n", node->str);
|
||||
}
|
||||
|
||||
// Check if node is an unexpanded array reference (AST_IDENTIFIER -> AST_MEMORY without indexing)
|
||||
static bool is_unexpanded_array_ref(AstNode *node)
|
||||
{
|
||||
if (node->type != AST_IDENTIFIER)
|
||||
return false;
|
||||
if (node->id2ast == nullptr || node->id2ast->type != AST_MEMORY)
|
||||
return false;
|
||||
// No indexing children = whole array reference
|
||||
return node->children.empty();
|
||||
}
|
||||
|
||||
// Check if two memories have compatible unpacked dimensions for array assignment
|
||||
static bool arrays_have_compatible_dims(AstNode *mem_a, AstNode *mem_b)
|
||||
{
|
||||
if (mem_a->unpacked_dimensions != mem_b->unpacked_dimensions)
|
||||
return false;
|
||||
for (int i = 0; i < mem_a->unpacked_dimensions; i++) {
|
||||
if (mem_a->dimensions[i].range_width != mem_b->dimensions[i].range_width)
|
||||
return false;
|
||||
}
|
||||
// Also check packed dimensions (element width)
|
||||
int a_width, a_size, a_bits;
|
||||
int b_width, b_size, b_bits;
|
||||
mem_a->meminfo(a_width, a_size, a_bits);
|
||||
mem_b->meminfo(b_width, b_size, b_bits);
|
||||
return a_width == b_width;
|
||||
}
|
||||
|
||||
// Convert per-dimension element positions to declared index values.
|
||||
// Position 0 is the first declared element for each unpacked dimension.
|
||||
static std::vector<int> array_indices_from_position(AstNode *mem, const std::vector<int> &position)
|
||||
{
|
||||
int num_dims = mem->unpacked_dimensions;
|
||||
log_assert(GetSize(position) == num_dims);
|
||||
|
||||
std::vector<int> indices(num_dims);
|
||||
for (int d = 0; d < num_dims; d++) {
|
||||
int low = mem->dimensions[d].range_right;
|
||||
int high = low + mem->dimensions[d].range_width - 1;
|
||||
indices[d] = mem->dimensions[d].range_swapped ? (low + position[d]) : (high - position[d]);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
// Generate all element positions for a multi-dimensional unpacked array and
|
||||
// call callback once for each combination.
|
||||
static void foreach_array_position(AstNode *mem, std::function<void(const std::vector<int>&)> callback)
|
||||
{
|
||||
int num_dims = mem->unpacked_dimensions;
|
||||
if (num_dims == 0) {
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> position(num_dims, 0);
|
||||
std::vector<int> sizes(num_dims);
|
||||
|
||||
for (int d = 0; d < num_dims; d++)
|
||||
sizes[d] = mem->dimensions[d].range_width;
|
||||
|
||||
// Iterate through all position combinations (rightmost dimension fastest).
|
||||
while (true) {
|
||||
callback(position);
|
||||
|
||||
int d = num_dims - 1;
|
||||
while (d >= 0) {
|
||||
position[d]++;
|
||||
if (position[d] < sizes[d])
|
||||
break;
|
||||
position[d] = 0;
|
||||
d--;
|
||||
}
|
||||
if (d < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int size_packed_struct(AstNode *snode, int base_offset)
|
||||
{
|
||||
// Struct members will be laid out in the structure contiguously from left to right.
|
||||
|
|
@ -3200,6 +3277,123 @@ skip_dynamic_range_lvalue_expansion:;
|
|||
}
|
||||
}
|
||||
|
||||
// Expand array assignment: arr_out = arr_in OR arr_out = cond ? arr_a : arr_b
|
||||
// Supports multi-dimensional unpacked arrays
|
||||
if ((type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE || type == AST_ASSIGN) &&
|
||||
is_unexpanded_array_ref(children[0].get()))
|
||||
{
|
||||
AstNode *lhs = children[0].get();
|
||||
AstNode *rhs = children[1].get();
|
||||
AstNode *lhs_mem = lhs->id2ast;
|
||||
|
||||
// Case 1: Direct array assignment (b = a)
|
||||
bool is_direct_assign = is_unexpanded_array_ref(rhs);
|
||||
|
||||
// Case 2: Ternary array assignment (out = sel ? a : b)
|
||||
bool is_ternary_assign = (rhs->type == AST_TERNARY &&
|
||||
is_unexpanded_array_ref(rhs->children[1].get()) &&
|
||||
is_unexpanded_array_ref(rhs->children[2].get()));
|
||||
|
||||
if (is_direct_assign || is_ternary_assign)
|
||||
{
|
||||
AstNode *direct_rhs_mem = nullptr;
|
||||
AstNode *true_mem = nullptr;
|
||||
AstNode *false_mem = nullptr;
|
||||
|
||||
// Validate array compatibility
|
||||
if (is_direct_assign) {
|
||||
direct_rhs_mem = rhs->id2ast;
|
||||
if (!arrays_have_compatible_dims(lhs_mem, direct_rhs_mem))
|
||||
input_error("Array dimension mismatch in assignment\n");
|
||||
} else {
|
||||
true_mem = rhs->children[1]->id2ast;
|
||||
false_mem = rhs->children[2]->id2ast;
|
||||
if (!arrays_have_compatible_dims(lhs_mem, true_mem) ||
|
||||
!arrays_have_compatible_dims(lhs_mem, false_mem))
|
||||
input_error("Array dimension mismatch in ternary expression\n");
|
||||
}
|
||||
|
||||
int num_dims = lhs_mem->unpacked_dimensions;
|
||||
|
||||
// Helper to add index to an identifier clone
|
||||
auto add_indices_to_id = [&](std::unique_ptr<AstNode> id, const std::vector<int>& indices) {
|
||||
if (num_dims == 1) {
|
||||
// Single dimension: use AST_RANGE
|
||||
id->children.push_back(std::make_unique<AstNode>(location, AST_RANGE,
|
||||
mkconst_int(location, indices[0], true)));
|
||||
} else {
|
||||
// Multiple dimensions: use AST_MULTIRANGE
|
||||
auto multirange = std::make_unique<AstNode>(location, AST_MULTIRANGE);
|
||||
for (int idx : indices) {
|
||||
multirange->children.push_back(std::make_unique<AstNode>(location, AST_RANGE,
|
||||
mkconst_int(location, idx, true)));
|
||||
}
|
||||
id->children.push_back(std::move(multirange));
|
||||
}
|
||||
id->integer = num_dims;
|
||||
// Reset basic_prep so multirange gets resolved during subsequent simplify passes
|
||||
id->basic_prep = false;
|
||||
return id;
|
||||
};
|
||||
|
||||
// Calculate total number of elements and warn if large
|
||||
int total_elements = 1;
|
||||
for (int d = 0; d < num_dims; d++)
|
||||
total_elements *= lhs_mem->dimensions[d].range_width;
|
||||
if (total_elements > 10000)
|
||||
log_warning("Expanding array assignment with %d elements at %s, this may be slow.\n",
|
||||
total_elements, location.to_string().c_str());
|
||||
|
||||
// Collect all assignments
|
||||
std::vector<std::unique_ptr<AstNode>> assignments;
|
||||
|
||||
foreach_array_position(lhs_mem, [&](const std::vector<int>& position) {
|
||||
auto lhs_indices = array_indices_from_position(lhs_mem, position);
|
||||
auto lhs_idx = add_indices_to_id(lhs->clone(), lhs_indices);
|
||||
|
||||
std::unique_ptr<AstNode> rhs_expr;
|
||||
if (is_direct_assign) {
|
||||
auto rhs_indices = array_indices_from_position(direct_rhs_mem, position);
|
||||
rhs_expr = add_indices_to_id(rhs->clone(), rhs_indices);
|
||||
} else {
|
||||
// Ternary case
|
||||
AstNode *cond = rhs->children[0].get();
|
||||
AstNode *true_val = rhs->children[1].get();
|
||||
AstNode *false_val = rhs->children[2].get();
|
||||
|
||||
auto true_indices = array_indices_from_position(true_mem, position);
|
||||
auto false_indices = array_indices_from_position(false_mem, position);
|
||||
auto true_idx = add_indices_to_id(true_val->clone(), true_indices);
|
||||
auto false_idx = add_indices_to_id(false_val->clone(), false_indices);
|
||||
|
||||
rhs_expr = std::make_unique<AstNode>(location, AST_TERNARY,
|
||||
cond->clone(), std::move(true_idx), std::move(false_idx));
|
||||
}
|
||||
|
||||
auto assign = std::make_unique<AstNode>(location, type,
|
||||
std::move(lhs_idx), std::move(rhs_expr));
|
||||
assign->was_checked = true;
|
||||
assignments.push_back(std::move(assign));
|
||||
});
|
||||
|
||||
// For continuous assignments, add to module; for procedural, use block
|
||||
if (type == AST_ASSIGN) {
|
||||
// Add all but last to module
|
||||
for (size_t i = 0; i + 1 < assignments.size(); i++)
|
||||
current_ast_mod->children.push_back(std::move(assignments[i]));
|
||||
// Last one replaces current node
|
||||
newNode = std::move(assignments.back());
|
||||
} else {
|
||||
// Wrap in AST_BLOCK for procedural
|
||||
newNode = std::make_unique<AstNode>(location, AST_BLOCK);
|
||||
for (auto& assign : assignments)
|
||||
newNode->children.push_back(std::move(assign));
|
||||
}
|
||||
|
||||
goto apply_newNode;
|
||||
}
|
||||
}
|
||||
|
||||
// assignment with memory in left-hand side expression -> replace with memory write port
|
||||
if (stage > 1 && (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_IDENTIFIER &&
|
||||
children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY && children[0]->id2ast->children.size() >= 2 &&
|
||||
|
|
|
|||
87
tests/svtypes/array_assign.sv
Normal file
87
tests/svtypes/array_assign.sv
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Test for array-to-array assignment and ternary expressions
|
||||
|
||||
`define STRINGIFY(x) `"x`"
|
||||
`define STATIC_ASSERT(x) if(!(x)) $error({"assert failed: ", `STRINGIFY(x)})
|
||||
|
||||
module top;
|
||||
// Test 1: Basic array ternary with continuous assignment
|
||||
reg [7:0] a1[4];
|
||||
reg [7:0] b1[4];
|
||||
wire [7:0] out1[4];
|
||||
wire sel1;
|
||||
assign out1 = sel1 ? a1 : b1;
|
||||
`STATIC_ASSERT($bits(out1) == 32);
|
||||
|
||||
// Test 2: Non-zero base index
|
||||
reg [7:0] a2[3:6];
|
||||
reg [7:0] b2[3:6];
|
||||
wire [7:0] out2[3:6];
|
||||
wire sel2;
|
||||
assign out2 = sel2 ? a2 : b2;
|
||||
`STATIC_ASSERT($bits(out2) == 32);
|
||||
|
||||
// Test 3: Single-bit elements
|
||||
reg a3[8];
|
||||
reg b3[8];
|
||||
wire out3[8];
|
||||
wire sel3;
|
||||
assign out3 = sel3 ? a3 : b3;
|
||||
`STATIC_ASSERT($bits(out3) == 8);
|
||||
|
||||
// Test 4: Multi-dimensional array ternary
|
||||
reg [7:0] a4[2][3];
|
||||
reg [7:0] b4[2][3];
|
||||
wire [7:0] out4[2][3];
|
||||
wire sel4;
|
||||
assign out4 = sel4 ? a4 : b4;
|
||||
`STATIC_ASSERT($bits(out4) == 48);
|
||||
|
||||
// Test 5: Direct array assignment (continuous)
|
||||
reg [7:0] a5[4];
|
||||
wire [7:0] b5[4];
|
||||
assign b5 = a5;
|
||||
`STATIC_ASSERT($bits(b5) == 32);
|
||||
|
||||
// Test 6: Multi-dimensional direct assignment (continuous)
|
||||
reg [7:0] a6[2][3];
|
||||
wire [7:0] b6[2][3];
|
||||
assign b6 = a6;
|
||||
`STATIC_ASSERT($bits(b6) == 48);
|
||||
|
||||
// Test 7: Procedural direct assignment with different unpacked index ranges
|
||||
// Covers the AST_BLOCK expansion path for AST_ASSIGN_EQ.
|
||||
logic pa [1:0][1:0];
|
||||
logic pb [1:0][0:1];
|
||||
always_comb begin
|
||||
pa[0][0] = 1'b0;
|
||||
pa[0][1] = 1'b1;
|
||||
pa[1][0] = 1'b1;
|
||||
pa[1][1] = 1'b1;
|
||||
|
||||
pb = pa;
|
||||
|
||||
assert(pb[0][1] == 1'b0);
|
||||
assert(pb[0][0] == 1'b1);
|
||||
assert(pb[1][1] == 1'b1);
|
||||
assert(pb[1][0] == 1'b1);
|
||||
end
|
||||
|
||||
// Test 8: Procedural ternary assignment on arrays
|
||||
// Covers the AST_BLOCK expansion path for ternary RHS.
|
||||
logic pt_a [1:0];
|
||||
logic pt_b [1:0];
|
||||
logic pt_o [1:0];
|
||||
logic pt_sel;
|
||||
always_comb begin
|
||||
pt_a[0] = 1'b0;
|
||||
pt_a[1] = 1'b1;
|
||||
pt_b[0] = 1'b1;
|
||||
pt_b[1] = 1'b0;
|
||||
pt_sel = 1'b1;
|
||||
|
||||
pt_o = pt_sel ? pt_a : pt_b;
|
||||
|
||||
assert(pt_o[0] == 1'b0);
|
||||
assert(pt_o[1] == 1'b1);
|
||||
end
|
||||
endmodule
|
||||
Loading…
Add table
Add a link
Reference in a new issue