diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 984c4294c..c190bc7d4 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -100,6 +100,7 @@ std::string AST::type2str(AstNodeType type) X(AST_CAST_SIZE) X(AST_CONCAT) X(AST_REPLICATE) + X(AST_ASSIGN_PATTERN) X(AST_BIT_NOT) X(AST_BIT_AND) X(AST_BIT_OR) @@ -696,6 +697,16 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const fprintf(f, "}}"); break; + case AST_ASSIGN_PATTERN: + fprintf(f, "'{"); + for (int i = 0; i < GetSize(children); i++) { + if (i != 0) + fprintf(f, ", "); + children[i]->dumpVlog(f, ""); + } + fprintf(f, "}"); + break; + if (0) { case AST_BIT_NOT: txt = "~"; } if (0) { case AST_REDUCE_AND: txt = "&"; } if (0) { case AST_REDUCE_OR: txt = "|"; } diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index fd8ecddd7..f92b4a5b8 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -78,6 +78,7 @@ namespace AST AST_CAST_SIZE, AST_CONCAT, AST_REPLICATE, + AST_ASSIGN_PATTERN, AST_BIT_NOT, AST_BIT_AND, AST_BIT_OR, diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 966fe563a..d9bafcd3a 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -1212,6 +1212,15 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun sign_hint = false; break; + case AST_ASSIGN_PATTERN: + for (auto& child : children) { + sub_width_hint = 0; + sub_sign_hint = true; + child->detectSignWidthWorker(sub_width_hint, sub_sign_hint); + } + sign_hint = false; + break; + case AST_NEG: case AST_BIT_NOT: case AST_POS: @@ -1824,6 +1833,9 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) return sig; } + case AST_ASSIGN_PATTERN: + input_error("Assignment pattern is only supported for whole unpacked array assignments.\n"); + // generate cells for unary operations: $not, $pos, $neg if (0) { case AST_BIT_NOT: type_name = ID($not); } if (0) { case AST_POS: type_name = ID($pos); } diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index f314ff3d5..48a4291d2 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -91,6 +91,11 @@ void AstNode::fixup_hierarchy_flags(bool force_descend) children[0]->set_in_param_flag(true, force_descend); break; + case AST_ASSIGN_PATTERN: + for (auto& child : children) + child->set_in_param_flag(in_param, force_descend); + break; + case AST_GENFOR: case AST_FOR: for (auto& child : children) { @@ -297,6 +302,23 @@ static bool arrays_have_compatible_dims(AstNode *mem_a, AstNode *mem_b) return a_width == b_width; } +// Check if mem_b matches mem_a's unpacked dimensions starting at first_dim. +static bool arrays_have_compatible_dims_from(AstNode *mem_a, int first_dim, AstNode *mem_b) +{ + if (mem_b->unpacked_dimensions != mem_a->unpacked_dimensions - first_dim) + return false; + for (int i = 0; i < mem_b->unpacked_dimensions; i++) { + if (mem_a->dimensions[first_dim + 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 array_indices_from_position(AstNode *mem, const std::vector &position) @@ -1730,6 +1752,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin children_are_self_determined = true; break; + case AST_ASSIGN_PATTERN: + // Assignment pattern elements are context-determined by the target element type. + // Keep child width context intact until whole-array assignment expansion creates scalar assignments. + detect_width_simple = true; + break; + case AST_NEG: case AST_BIT_NOT: case AST_POS: @@ -3277,7 +3305,7 @@ skip_dynamic_range_lvalue_expansion:; } } - // Expand array assignment: arr_out = arr_in OR arr_out = cond ? arr_a : arr_b + // Expand array assignment: arr_out = arr_in OR arr_out = cond ? arr_a : arr_b OR arr_out = '{a, 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())) @@ -3294,30 +3322,27 @@ skip_dynamic_range_lvalue_expansion:; is_unexpanded_array_ref(rhs->children[1].get()) && is_unexpanded_array_ref(rhs->children[2].get())); - if (is_direct_assign || is_ternary_assign) + // Case 3: Positional assignment pattern (out = '{a, b}) + bool is_pattern_assign = rhs->type == AST_ASSIGN_PATTERN; + + if (is_direct_assign || is_ternary_assign || is_pattern_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; + int total_elements = 1; + for (int d = 0; d < num_dims; d++) + total_elements *= lhs_mem->dimensions[d].range_width; + int element_width, mem_size, addr_bits; + lhs_mem->meminfo(element_width, mem_size, addr_bits); + bool pattern_is_flat = false; - // Helper to add index to an identifier clone + // Helper to add indices to an array identifier clone. auto add_indices_to_id = [&](std::unique_ptr id, const std::vector& indices) { - if (num_dims == 1) { + int indexed_dims = GetSize(indices); + if (indexed_dims == 1) { // Single dimension: use AST_RANGE id->children.push_back(std::make_unique(location, AST_RANGE, mkconst_int(location, indices[0], true))); @@ -3330,44 +3355,139 @@ skip_dynamic_range_lvalue_expansion:; } id->children.push_back(std::move(multirange)); } - id->integer = num_dims; + id->integer = indexed_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; + auto add_position_to_id = [&](std::unique_ptr id, AstNode *mem, const std::vector& position) { + return add_indices_to_id(std::move(id), array_indices_from_position(mem, position)); + }; + + // Validate nested assignment pattern shape against unpacked dimensions. + std::function validate_pattern_shape = [&](AstNode *pattern, int dim) { + log_assert(pattern->type == AST_ASSIGN_PATTERN); + + int expected = lhs_mem->dimensions[dim].range_width; + if (GetSize(pattern->children) != expected) + input_error("Assignment pattern element count mismatch at dimension %d: got %d, expected %d\n", + dim + 1, GetSize(pattern->children), expected); + + if (dim + 1 == num_dims) + return; + + for (auto& child : pattern->children) { + if (child->type == AST_ASSIGN_PATTERN) { + validate_pattern_shape(child.get(), dim + 1); + } else if (is_unexpanded_array_ref(child.get()) && + arrays_have_compatible_dims_from(lhs_mem, dim + 1, child->id2ast)) { + continue; + } else { + input_error("Nested assignment pattern or compatible array expression required for dimension %d\n", dim + 2); + } + } + }; + + // Select the assignment pattern element for an unpacked array position. + auto pattern_element_at_position = [&](const std::vector& position, int flat_index) { + if (pattern_is_flat) + return rhs->children[flat_index]->clone(); + + AstNode *pattern = rhs; + for (int d = 0; d < num_dims; d++) { + log_assert(pattern->type == AST_ASSIGN_PATTERN); + AstNode *element = pattern->children[position[d]].get(); + + if (d + 1 == num_dims) + return element->clone(); + + if (element->type == AST_ASSIGN_PATTERN) { + pattern = element; + } else { + std::vector subposition(position.begin() + d + 1, position.end()); + return add_position_to_id(element->clone(), element->id2ast, subposition); + } + } + log_abort(); + }; + + // 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 if (is_ternary_assign) { + 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"); + } else { + if (num_dims > 1 && GetSize(rhs->children) == lhs_mem->dimensions[0].range_width) { + validate_pattern_shape(rhs, 0); + } else if (num_dims == 1 && GetSize(rhs->children) == total_elements) { + pattern_is_flat = true; + } else { + if (num_dims > 1 && GetSize(rhs->children) == lhs_mem->dimensions[0].range_width) + validate_pattern_shape(rhs, 0); + int expected = num_dims > 1 ? lhs_mem->dimensions[0].range_width : total_elements; + input_error("Assignment pattern element count mismatch: got %d, expected %d\n", GetSize(rhs->children), expected); + } + } + + // Warn if array assignment expansion is large. 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> assignments; + std::vector> pattern_temp_assignments; foreach_array_position(lhs_mem, [&](const std::vector& position) { - auto lhs_indices = array_indices_from_position(lhs_mem, position); - auto lhs_idx = add_indices_to_id(lhs->clone(), lhs_indices); + auto lhs_idx = add_position_to_id(lhs->clone(), lhs_mem, position); std::unique_ptr 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 { + rhs_expr = add_position_to_id(rhs->clone(), direct_rhs_mem, position); + } else if (is_ternary_assign) { // 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); + auto true_idx = add_position_to_id(true_val->clone(), true_mem, position); + auto false_idx = add_position_to_id(false_val->clone(), false_mem, position); rhs_expr = std::make_unique(location, AST_TERNARY, cond->clone(), std::move(true_idx), std::move(false_idx)); + } else { + auto pattern_rhs = pattern_element_at_position(position, GetSize(assignments)); + + if (type == AST_ASSIGN_EQ) { + auto wire_tmp_owned = std::make_unique(location, AST_WIRE, + std::make_unique(location, AST_RANGE, + mkconst_int(location, element_width - 1, true), + mkconst_int(location, 0, true))); + auto wire_tmp = wire_tmp_owned.get(); + wire_tmp->str = stringf("$assignpattern$%s:%d$%d", + RTLIL::encode_filename(*location.begin.filename), location.begin.line, autoidx++); + current_scope[wire_tmp->str] = wire_tmp; + current_ast_mod->children.push_back(std::move(wire_tmp_owned)); + wire_tmp->set_attribute(ID::nosync, AstNode::mkconst_int(location, 1, false)); + while (wire_tmp->simplify(true, 1, -1, false)) { } + wire_tmp->is_logic = true; + wire_tmp->is_signed = lhs_mem->is_signed; + + auto tmp_id = std::make_unique(location, AST_IDENTIFIER); + tmp_id->str = wire_tmp->str; + pattern_temp_assignments.push_back(std::make_unique(location, AST_ASSIGN_EQ, + tmp_id->clone(), std::move(pattern_rhs))); + rhs_expr = std::move(tmp_id); + } else { + rhs_expr = std::move(pattern_rhs); + } } auto assign = std::make_unique(location, type, @@ -3386,6 +3506,8 @@ skip_dynamic_range_lvalue_expansion:; } else { // Wrap in AST_BLOCK for procedural newNode = std::make_unique(location, AST_BLOCK); + for (auto& assign : pattern_temp_assignments) + newNode->children.push_back(std::move(assign)); for (auto& assign : assignments) newNode->children.push_back(std::move(assign)); } @@ -4652,6 +4774,8 @@ replace_fcall_later:; tmp_bits.insert(tmp_bits.end(), children.at(1)->bits.begin(), children.at(1)->bits.end()); newNode = children.at(1)->is_string ? mkconst_str(location, tmp_bits) : mkconst_bits(location, tmp_bits, false); break; + case AST_ASSIGN_PATTERN: + goto not_const; default: not_const: break; diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 148a6cc63..b394ce074 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -546,7 +546,7 @@ %token TOK_z "'z'" %type range range_or_multirange non_opt_range non_opt_multirange -%type wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list non_io_wire_type io_wire_type +%type wire_type expr basic_expr concat_list assignment_pattern_list rvalue lvalue lvalue_concat_list non_io_wire_type io_wire_type %type opt_label opt_sva_label tok_prim_wrapper hierarchical_id hierarchical_type_id integral_number %type type_name %type opt_enum_init enum_type struct_type enum_struct_type func_return_type typedef_base_type @@ -3349,6 +3349,11 @@ basic_expr: TOK_LCURL concat_list TOK_RCURL { $$ = std::move($2); } | + OP_CAST TOK_LCURL assignment_pattern_list optional_comma TOK_RCURL { + if (!mode->sv) + err_at_loc(@1, "Assignment patterns are only supported in SystemVerilog mode."); + $$ = std::move($3); + } | TOK_LCURL expr TOK_LCURL concat_list TOK_RCURL TOK_RCURL { $$ = std::make_unique(@$, AST_REPLICATE, std::move($2), std::move($4)); } | @@ -3580,6 +3585,16 @@ concat_list: $$->children.push_back(std::move($1)); }; +assignment_pattern_list: + expr { + $$ = std::make_unique(@$, AST_ASSIGN_PATTERN); + $$->children.push_back(std::move($1)); + } | + assignment_pattern_list TOK_COMMA expr { + $$ = std::move($1); + $$->children.push_back(std::move($3)); + }; + integral_number: TOK_CONSTVAL { $$ = std::move($1); } | TOK_UNBASED_UNSIZED_CONSTVAL { $$ = std::move($1); } | diff --git a/tests/svtypes/array_assign.sv b/tests/svtypes/array_assign.sv index a5ca6363c..f23a08d27 100644 --- a/tests/svtypes/array_assign.sv +++ b/tests/svtypes/array_assign.sv @@ -84,4 +84,85 @@ module top; assert(pt_o[0] == 1'b0); assert(pt_o[1] == 1'b1); end + + // Test 9: Positional assignment pattern on a whole unpacked array + // Covers the parser and continuous assignment expansion path for `'{...}. + wire ap_table [1]; + wire ap_i = 1'b0; + wire ap_out; + assign ap_table = '{1'h1}; + assign ap_out = ap_table[ap_i > 1'h0 ? 1'h0 : ap_i]; + always_comb begin + assert(ap_out == 1'b1); + end + + // Test 10: Positional assignment pattern preserves left-to-right element order. + wire ap_order [2]; + assign ap_order = '{1'b0, 1'b1}; + always_comb begin + assert(ap_order[0] == 1'b0); + assert(ap_order[1] == 1'b1); + end + + function automatic logic ap_identity(input logic value); + ap_identity = value; + endfunction + + // Test 11: The first assignment pattern element is a runtime expression. + wire ap_runtime_in = 1'b1; + wire ap_runtime [2]; + assign ap_runtime = '{ap_identity(ap_runtime_in), 1'b0}; + always_comb begin + assert(ap_runtime[0] == 1'b1); + assert(ap_runtime[1] == 1'b0); + end + + // Test 12: Nested positional assignment pattern on a multidimensional array. + wire ap_nested [2][2]; + assign ap_nested = '{'{1'b1, 1'b0}, '{1'b0, 1'b1}}; + always_comb begin + assert(ap_nested[0][0] == 1'b1); + assert(ap_nested[0][1] == 1'b0); + assert(ap_nested[1][0] == 1'b0); + assert(ap_nested[1][1] == 1'b1); + end + + // Test 13: Multidimensional assignment pattern with row expressions. + wire ap_row0 [2]; + wire ap_row1 [2]; + wire ap_rows [2][2]; + assign ap_row0 = '{1'b1, 1'b0}; + assign ap_row1 = '{1'b0, 1'b1}; + assign ap_rows = '{ap_row0, ap_row1}; + always_comb begin + assert(ap_rows[0][0] == 1'b1); + assert(ap_rows[0][1] == 1'b0); + assert(ap_rows[1][0] == 1'b0); + assert(ap_rows[1][1] == 1'b1); + end + + // Test 14: Procedural blocking assignment pattern preserves RHS values. + logic ap_swap [2]; + always_comb begin + ap_swap[0] = 1'b0; + ap_swap[1] = 1'b1; + ap_swap = '{ap_swap[1], ap_swap[0]}; + + assert(ap_swap[0] == 1'b1); + assert(ap_swap[1] == 1'b0); + end + + // Test 15: Assignment pattern elements use the target element width context. + logic [4:0] ap_width_ctx [1]; + assign ap_width_ctx = '{4'hf + 4'h1}; + always_comb begin + assert(ap_width_ctx[0] == 5'h10); + end + + // Test 16: Nested assignment pattern elements also use the target element width context. + logic [4:0] ap_nested_width_ctx [1][1]; + assign ap_nested_width_ctx = '{'{4'hf + 4'h1}}; + always_comb begin + assert(ap_nested_width_ctx[0][0] == 5'h10); + end endmodule diff --git a/tests/svtypes/array_assign_flat_multidim_pattern.ys b/tests/svtypes/array_assign_flat_multidim_pattern.ys new file mode 100644 index 000000000..a2e57e2f5 --- /dev/null +++ b/tests/svtypes/array_assign_flat_multidim_pattern.ys @@ -0,0 +1,10 @@ +logger -expect error "Assignment pattern element count mismatch: got 4, expected 2" 1 + +read_verilog -sv <