mirror of
https://github.com/YosysHQ/yosys
synced 2026-05-08 11:25:25 +00:00
Support positional assignment patterns for unpacked arrays
This commit is contained in:
parent
2dc69a7578
commit
390f09b89a
7 changed files with 286 additions and 32 deletions
|
|
@ -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 = "|"; }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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<int> array_indices_from_position(AstNode *mem, const std::vector<int> &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<AstNode> id, const std::vector<int>& 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<AstNode>(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<AstNode> id, AstNode *mem, const std::vector<int>& 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<void(AstNode*, int)> 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<int>& 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<int> 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<std::unique_ptr<AstNode>> assignments;
|
||||
std::vector<std::unique_ptr<AstNode>> pattern_temp_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);
|
||||
auto lhs_idx = add_position_to_id(lhs->clone(), lhs_mem, position);
|
||||
|
||||
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 {
|
||||
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<AstNode>(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<AstNode>(location, AST_WIRE,
|
||||
std::make_unique<AstNode>(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<AstNode>(location, AST_IDENTIFIER);
|
||||
tmp_id->str = wire_tmp->str;
|
||||
pattern_temp_assignments.push_back(std::make_unique<AstNode>(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<AstNode>(location, type,
|
||||
|
|
@ -3386,6 +3506,8 @@ skip_dynamic_range_lvalue_expansion:;
|
|||
} else {
|
||||
// Wrap in AST_BLOCK for procedural
|
||||
newNode = std::make_unique<AstNode>(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;
|
||||
|
|
|
|||
|
|
@ -546,7 +546,7 @@
|
|||
%token TOK_z "'z'"
|
||||
|
||||
%type <ast_t> range range_or_multirange non_opt_range non_opt_multirange
|
||||
%type <ast_t> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list non_io_wire_type io_wire_type
|
||||
%type <ast_t> wire_type expr basic_expr concat_list assignment_pattern_list rvalue lvalue lvalue_concat_list non_io_wire_type io_wire_type
|
||||
%type <string_t> opt_label opt_sva_label tok_prim_wrapper hierarchical_id hierarchical_type_id integral_number
|
||||
%type <string_t> type_name
|
||||
%type <ast_t> 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<AstNode>(@$, 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<AstNode>(@$, 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); } |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
tests/svtypes/array_assign_flat_multidim_pattern.ys
Normal file
10
tests/svtypes/array_assign_flat_multidim_pattern.ys
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
logger -expect error "Assignment pattern element count mismatch: got 4, expected 2" 1
|
||||
|
||||
read_verilog -sv <<EOT
|
||||
module top;
|
||||
wire a [2][2];
|
||||
assign a = '{1'b1, 1'b0, 1'b0, 1'b1};
|
||||
endmodule
|
||||
EOT
|
||||
|
||||
hierarchy -top top
|
||||
Loading…
Add table
Add a link
Reference in a new issue