Add support for packed multidimensional arrays

* Generalization of dimensions metadata (also simplifies $size et al.)
* Parsing and elaboration of multidimensional packed ranges
This commit is contained in:
Dag Lem 2024-01-25 07:28:15 +01:00 committed by Zachary Snow
parent ac0fb2e301
commit 39fea32c6e
7 changed files with 247 additions and 267 deletions

View File

@ -224,6 +224,7 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch
port_id = 0; port_id = 0;
range_left = -1; range_left = -1;
range_right = 0; range_right = 0;
unpacked_dimensions = 0;
integer = 0; integer = 0;
realvalue = 0; realvalue = 0;
id2ast = NULL; id2ast = NULL;
@ -349,17 +350,15 @@ void AstNode::dumpAst(FILE *f, std::string indent) const
fprintf(f, " int=%u", (int)integer); fprintf(f, " int=%u", (int)integer);
if (realvalue != 0) if (realvalue != 0)
fprintf(f, " real=%e", realvalue); fprintf(f, " real=%e", realvalue);
if (!multirange_dimensions.empty()) { if (!dimensions.empty()) {
fprintf(f, " multirange=["); fprintf(f, " dimensions=");
for (int v : multirange_dimensions) for (auto &dim : dimensions) {
fprintf(f, " %d", v); int left = dim.range_right + dim.range_width - 1;
fprintf(f, " ]"); int right = dim.range_right;
} if (dim.range_swapped)
if (!multirange_swapped.empty()) { std::swap(left, right);
fprintf(f, " multirange_swapped=["); fprintf(f, "[%d:%d]", left, right);
for (bool v : multirange_swapped) }
fprintf(f, " %d", v);
fprintf(f, " ]");
} }
if (is_enum) { if (is_enum) {
fprintf(f, " type=enum"); fprintf(f, " type=enum");
@ -505,6 +504,11 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const
} }
break; break;
case AST_MULTIRANGE:
for (auto child : children)
child->dumpVlog(f, "");
break;
case AST_ALWAYS: case AST_ALWAYS:
fprintf(f, "%s" "always @", indent.c_str()); fprintf(f, "%s" "always @", indent.c_str());
for (auto child : children) { for (auto child : children) {
@ -542,7 +546,7 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const
case AST_IDENTIFIER: case AST_IDENTIFIER:
{ {
AST::AstNode *member_node = AST::get_struct_member(this); AstNode *member_node = get_struct_member();
if (member_node) if (member_node)
fprintf(f, "%s[%d:%d]", id2vl(str).c_str(), member_node->range_left, member_node->range_right); fprintf(f, "%s[%d:%d]", id2vl(str).c_str(), member_node->range_left, member_node->range_right);
else else

View File

@ -202,9 +202,17 @@ namespace AST
// set for IDs typed to an enumeration, not used // set for IDs typed to an enumeration, not used
bool is_enum; bool is_enum;
// if this is a multirange memory then this vector contains offset and length of each dimension // Declared range for array dimension.
std::vector<int> multirange_dimensions; struct dimension_t {
std::vector<bool> multirange_swapped; // true if range is swapped int range_right; // lsb in [msb:lsb]
int range_width; // msb - lsb + 1
bool range_swapped; // if the declared msb < lsb, msb and lsb above are swapped
};
// Packed and unpacked dimensions for arrays.
// Unpacked dimensions go first, to follow the order of indexing.
std::vector<dimension_t> dimensions;
// Number of unpacked dimensions.
int unpacked_dimensions;
// this is set by simplify and used during RTLIL generation // this is set by simplify and used during RTLIL generation
AstNode *id2ast; AstNode *id2ast;
@ -371,6 +379,10 @@ namespace AST
// localized fixups after modifying children/attributes of a particular node // localized fixups after modifying children/attributes of a particular node
void fixup_hierarchy_flags(bool force_descend = false); void fixup_hierarchy_flags(bool force_descend = false);
// helpers for indexing
AstNode *make_index_range(AstNode *node, bool unpacked_range = false);
AstNode *get_struct_member() const;
// helper to print errors from simplify/genrtlil code // helper to print errors from simplify/genrtlil code
[[noreturn]] void input_error(const char *format, ...) const YS_ATTRIBUTE(format(printf, 2, 3)); [[noreturn]] void input_error(const char *format, ...) const YS_ATTRIBUTE(format(printf, 2, 3));
}; };
@ -416,10 +428,6 @@ namespace AST
// Helper for setting the src attribute. // Helper for setting the src attribute.
void set_src_attr(RTLIL::AttrObject *obj, const AstNode *ast); void set_src_attr(RTLIL::AttrObject *obj, const AstNode *ast);
// struct helper exposed from simplify for genrtlil
AstNode *make_struct_member_range(AstNode *node, AstNode *member_node);
AstNode *get_struct_member(const AstNode *node);
// generate standard $paramod... derived module name; parameters should be // generate standard $paramod... derived module name; parameters should be
// in the order they are declared in the instantiated module // in the order they are declared in the instantiated module
std::string derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> &parameters); std::string derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> &parameters);

View File

@ -1045,7 +1045,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun
if (children.size() > 1) if (children.size() > 1)
range = children[1]; range = children[1];
} else if (id_ast->type == AST_STRUCT_ITEM || id_ast->type == AST_STRUCT || id_ast->type == AST_UNION) { } else if (id_ast->type == AST_STRUCT_ITEM || id_ast->type == AST_STRUCT || id_ast->type == AST_UNION) {
AstNode *tmp_range = make_struct_member_range(this, id_ast); AstNode *tmp_range = make_index_range(id_ast);
this_width = tmp_range->range_left - tmp_range->range_right + 1; this_width = tmp_range->range_left - tmp_range->range_right + 1;
delete tmp_range; delete tmp_range;
} else } else
@ -1584,7 +1584,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
chunk.width = wire->width; chunk.width = wire->width;
chunk.offset = 0; chunk.offset = 0;
if ((member_node = get_struct_member(this))) { if ((member_node = get_struct_member())) {
// Clamp wire chunk to range of member within struct/union. // Clamp wire chunk to range of member within struct/union.
chunk.width = member_node->range_left - member_node->range_right + 1; chunk.width = member_node->range_left - member_node->range_right + 1;
chunk.offset = member_node->range_right; chunk.offset = member_node->range_right;

View File

@ -259,35 +259,24 @@ static int range_width(AstNode *node, AstNode *rnode)
{ {
log_assert(rnode->type==AST_RANGE); log_assert(rnode->type==AST_RANGE);
if (!rnode->range_valid) { if (!rnode->range_valid) {
node->input_error("Size must be constant in packed struct/union member %s\n", node->str.c_str()); node->input_error("Non-constant range in declaration of %s\n", node->str.c_str());
} }
// note: range swapping has already been checked for // note: range swapping has already been checked for
return rnode->range_left - rnode->range_right + 1; return rnode->range_left - rnode->range_right + 1;
} }
static int add_dimension(AstNode *node, AstNode *rnode)
{
int width = range_width(node, rnode);
node->dimensions.push_back({ rnode->range_right, width, rnode->range_swapped });
return width;
}
[[noreturn]] static void struct_array_packing_error(AstNode *node) [[noreturn]] static void struct_array_packing_error(AstNode *node)
{ {
node->input_error("Unpacked array in packed struct/union member %s\n", node->str.c_str()); node->input_error("Unpacked array in packed struct/union member %s\n", node->str.c_str());
} }
static void save_struct_range_dimensions(AstNode *node, AstNode *rnode)
{
node->multirange_dimensions.push_back(rnode->range_right);
node->multirange_dimensions.push_back(range_width(node, rnode));
node->multirange_swapped.push_back(rnode->range_swapped);
}
static int get_struct_range_offset(AstNode *node, int dimension)
{
return node->multirange_dimensions[2*dimension];
}
static int get_struct_range_width(AstNode *node, int dimension)
{
return node->multirange_dimensions[2*dimension + 1];
}
static int size_packed_struct(AstNode *snode, int base_offset) static int size_packed_struct(AstNode *snode, int base_offset)
{ {
// Struct members will be laid out in the structure contiguously from left to right. // Struct members will be laid out in the structure contiguously from left to right.
@ -303,10 +292,6 @@ static int size_packed_struct(AstNode *snode, int base_offset)
if (node->type == AST_STRUCT || node->type == AST_UNION) { if (node->type == AST_STRUCT || node->type == AST_UNION) {
// embedded struct or union // embedded struct or union
width = size_packed_struct(node, base_offset + offset); width = size_packed_struct(node, base_offset + offset);
// set range of struct
node->range_right = base_offset + offset;
node->range_left = base_offset + offset + width - 1;
node->range_valid = true;
} }
else { else {
log_assert(node->type == AST_STRUCT_ITEM); log_assert(node->type == AST_STRUCT_ITEM);
@ -318,18 +303,16 @@ static int size_packed_struct(AstNode *snode, int base_offset)
// and integer data types are allowed in packed structs / unions in SystemVerilog. // and integer data types are allowed in packed structs / unions in SystemVerilog.
if (node->children[1]->type == AST_RANGE) { if (node->children[1]->type == AST_RANGE) {
// Unpacked array, e.g. bit [63:0] a [0:3] // Unpacked array, e.g. bit [63:0] a [0:3]
// Pretend it's declared as a packed array, e.g. bit [0:3][63:0] a
auto rnode = node->children[1]; auto rnode = node->children[1];
if (rnode->children.size() == 1) { if (rnode->children.size() == 1) {
// C-style array size, e.g. bit [63:0] a [4] // C-style array size, e.g. bit [63:0] a [4]
node->multirange_dimensions.push_back(0); node->dimensions.push_back({ 0, rnode->range_left, true });
node->multirange_dimensions.push_back(rnode->range_left);
node->multirange_swapped.push_back(true);
width *= rnode->range_left; width *= rnode->range_left;
} else { } else {
save_struct_range_dimensions(node, rnode); width *= add_dimension(node, rnode);
width *= range_width(node, rnode);
} }
save_struct_range_dimensions(node, node->children[0]); add_dimension(node, node->children[0]);
} }
else { else {
// The Yosys extension for unpacked arrays in packed structs / unions // The Yosys extension for unpacked arrays in packed structs / unions
@ -338,7 +321,7 @@ static int size_packed_struct(AstNode *snode, int base_offset)
} }
} else { } else {
// Vector // Vector
save_struct_range_dimensions(node, node->children[0]); add_dimension(node, node->children[0]);
} }
// range nodes are now redundant // range nodes are now redundant
for (AstNode *child : node->children) for (AstNode *child : node->children)
@ -354,8 +337,7 @@ static int size_packed_struct(AstNode *snode, int base_offset)
} }
width = 1; width = 1;
for (auto rnode : node->children[0]->children) { for (auto rnode : node->children[0]->children) {
save_struct_range_dimensions(node, rnode); width *= add_dimension(node, rnode);
width *= range_width(node, rnode);
} }
// range nodes are now redundant // range nodes are now redundant
for (AstNode *child : node->children) for (AstNode *child : node->children)
@ -365,6 +347,7 @@ static int size_packed_struct(AstNode *snode, int base_offset)
else if (node->range_left < 0) { else if (node->range_left < 0) {
// 1 bit signal: bit, logic or reg // 1 bit signal: bit, logic or reg
width = 1; width = 1;
node->dimensions.push_back({ 0, width, false });
} }
else { else {
// already resolved and compacted // already resolved and compacted
@ -395,12 +378,16 @@ static int size_packed_struct(AstNode *snode, int base_offset)
offset += width; offset += width;
} }
} }
return (is_union ? packed_width : offset);
}
[[noreturn]] static void struct_op_error(AstNode *node) int width = is_union ? packed_width : offset;
{
node->input_error("Unsupported operation for struct/union member %s\n", node->str.c_str()+1); snode->range_right = base_offset;
snode->range_left = base_offset + width - 1;
snode->range_valid = true;
if (snode->dimensions.empty())
snode->dimensions.push_back({ 0, width, false });
return width;
} }
static AstNode *node_int(int ival) static AstNode *node_int(int ival)
@ -413,113 +400,123 @@ static AstNode *multiply_by_const(AstNode *expr_node, int stride)
return new AstNode(AST_MUL, expr_node, node_int(stride)); return new AstNode(AST_MUL, expr_node, node_int(stride));
} }
static AstNode *normalize_struct_index(AstNode *expr, AstNode *member_node, int dimension) static AstNode *normalize_index(AstNode *expr, AstNode *decl_node, int dimension)
{ {
expr = expr->clone(); expr = expr->clone();
int offset = get_struct_range_offset(member_node, dimension); int offset = decl_node->dimensions[dimension].range_right;
if (offset) { if (offset) {
expr = new AstNode(AST_SUB, expr, node_int(offset)); expr = new AstNode(AST_SUB, expr, node_int(offset));
} }
if (member_node->multirange_swapped[dimension]) { // Packed dimensions are normally indexed by lsb, while unpacked dimensions are normally indexed by msb.
// The dimension has swapped range; swap index into the struct accordingly. if ((dimension < decl_node->unpacked_dimensions) ^ decl_node->dimensions[dimension].range_swapped) {
int msb = get_struct_range_width(member_node, dimension) - 1; // Swap the index if the dimension is declared the "wrong" way.
expr = new AstNode(AST_SUB, node_int(msb), expr); int left = decl_node->dimensions[dimension].range_width - 1;
expr = new AstNode(AST_SUB, node_int(left), expr);
} }
return expr; return expr;
} }
static AstNode *struct_index_lsb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int &stride) static AstNode *index_offset(AstNode *offset, AstNode *rnode, AstNode *decl_node, int dimension, int &stride)
{ {
stride /= get_struct_range_width(member_node, dimension); stride /= decl_node->dimensions[dimension].range_width;
auto right = normalize_struct_index(rnode->children.back(), member_node, dimension); auto right = normalize_index(rnode->children.back(), decl_node, dimension);
auto offset = stride > 1 ? multiply_by_const(right, stride) : right; auto add_offset = stride > 1 ? multiply_by_const(right, stride) : right;
return lsb_offset ? new AstNode(AST_ADD, lsb_offset, offset) : offset; return offset ? new AstNode(AST_ADD, offset, add_offset) : add_offset;
} }
static AstNode *struct_index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int stride) static AstNode *index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *decl_node, int dimension, int stride)
{ {
log_assert(rnode->children.size() <= 2); log_assert(rnode->children.size() <= 2);
// Offset to add to LSB // Offset to add to LSB
AstNode *offset; AstNode *add_offset;
if (rnode->children.size() == 1) { if (rnode->children.size() == 1) {
// Index, e.g. s.a[i] // Index, e.g. s.a[i]
offset = node_int(stride - 1); add_offset = node_int(stride - 1);
} }
else { else {
// rnode->children.size() == 2 // rnode->children.size() == 2
// Slice, e.g. s.a[i:j] // Slice, e.g. s.a[i:j]
auto left = normalize_struct_index(rnode->children[0], member_node, dimension); auto left = normalize_index(rnode->children[0], decl_node, dimension);
auto right = normalize_struct_index(rnode->children[1], member_node, dimension); auto right = normalize_index(rnode->children[1], decl_node, dimension);
offset = new AstNode(AST_SUB, left, right); add_offset = new AstNode(AST_SUB, left, right);
if (stride > 1) { if (stride > 1) {
// offset = (msb - lsb + 1)*stride - 1 // offset = (msb - lsb + 1)*stride - 1
auto slice_width = new AstNode(AST_ADD, offset, node_int(1)); auto slice_width = new AstNode(AST_ADD, add_offset, node_int(1));
offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1)); add_offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1));
} }
} }
return new AstNode(AST_ADD, lsb_offset, offset); return new AstNode(AST_ADD, lsb_offset, add_offset);
} }
AstNode *AST::make_struct_member_range(AstNode *node, AstNode *member_node) AstNode *AstNode::make_index_range(AstNode *decl_node, bool unpacked_range)
{ {
// Work out the range in the packed array that corresponds to a struct member // Work out the range in the packed array that corresponds to a struct member
// taking into account any range operations applicable to the current node // taking into account any range operations applicable to the current node
// such as array indexing or slicing // such as array indexing or slicing
int range_left = member_node->range_left; if (children.empty()) {
int range_right = member_node->range_right;
if (node->children.empty()) {
// no range operations apply, return the whole width // no range operations apply, return the whole width
return make_range(range_left - range_right, 0); return make_range(decl_node->range_left - decl_node->range_right, 0);
} }
if (node->children.size() != 1) { log_assert(children.size() == 1);
struct_op_error(node);
}
// Range operations // Range operations
auto rnode = node->children[0]; AstNode *rnode = children[0];
AstNode *lsb_offset = NULL; AstNode *offset = NULL;
int stride = range_left - range_right + 1; int dim = unpacked_range ? 0 : decl_node->unpacked_dimensions;
size_t i = 0; int max_dim = unpacked_range ? decl_node->unpacked_dimensions : GetSize(decl_node->dimensions);
int stride = 1;
for (int i = dim; i < max_dim; i++) {
stride *= decl_node->dimensions[i].range_width;
}
// Calculate LSB offset for the final index / slice // Calculate LSB offset for the final index / slice
if (rnode->type == AST_RANGE) { if (rnode->type == AST_RANGE) {
lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); offset = index_offset(offset, rnode, decl_node, dim, stride);
} }
else if (rnode->type == AST_MULTIRANGE) { else if (rnode->type == AST_MULTIRANGE) {
// Add offset for each dimension // Add offset for each dimension
auto mrnode = rnode; AstNode *mrnode = rnode;
for (i = 0; i < mrnode->children.size(); i++) { int stop_dim = std::min(GetSize(mrnode->children), max_dim);
rnode = mrnode->children[i]; for (; dim < stop_dim; dim++) {
lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); rnode = mrnode->children[dim];
offset = index_offset(offset, rnode, decl_node, dim, stride);
} }
i--; // Step back to the final index / slice dim--; // Step back to the final index / slice
} }
else { else {
struct_op_error(node); input_error("Unsupported range operation for %s\n", str.c_str());
} }
// Calculate MSB offset for the final index / slice AstNode *index_range = new AstNode(AST_RANGE);
auto msb_offset = struct_index_msb_offset(lsb_offset->clone(), rnode, member_node, i, stride);
return new AstNode(AST_RANGE, msb_offset, lsb_offset); if (!unpacked_range && (stride > 1 || GetSize(rnode->children) == 2)) {
// Calculate MSB offset for the final index / slice of packed dimensions.
AstNode *msb_offset = index_msb_offset(offset->clone(), rnode, decl_node, dim, stride);
index_range->children.push_back(msb_offset);
}
index_range->children.push_back(offset);
return index_range;
} }
AstNode *AST::get_struct_member(const AstNode *node) AstNode *AstNode::get_struct_member() const
{ {
AST::AstNode *member_node; AstNode *member_node;
if (node->attributes.count(ID::wiretype) && (member_node = node->attributes.at(ID::wiretype)) && if (attributes.count(ID::wiretype) && (member_node = attributes.at(ID::wiretype)) &&
(member_node->type == AST_STRUCT_ITEM || member_node->type == AST_STRUCT || member_node->type == AST_UNION)) (member_node->type == AST_STRUCT_ITEM || member_node->type == AST_STRUCT || member_node->type == AST_UNION))
{ {
return member_node; return member_node;
} }
return NULL; return nullptr;
} }
static void add_members_to_scope(AstNode *snode, std::string name) static void add_members_to_scope(AstNode *snode, std::string name)
@ -537,22 +534,10 @@ static void add_members_to_scope(AstNode *snode, std::string name)
} }
} }
static int get_max_offset(AstNode *node)
{
// get the width from the MS member in the struct
// as members are laid out from left to right in the packed wire
log_assert(node->type==AST_STRUCT || node->type==AST_UNION);
while (node->type != AST_STRUCT_ITEM) {
node = node->children[0];
}
return node->range_left;
}
static AstNode *make_packed_struct(AstNode *template_node, std::string &name, decltype(AstNode::attributes) &attributes) static AstNode *make_packed_struct(AstNode *template_node, std::string &name, decltype(AstNode::attributes) &attributes)
{ {
// create a wire for the packed struct // create a wire for the packed struct
int offset = get_max_offset(template_node); auto wnode = new AstNode(AST_WIRE, make_range(template_node->range_left, 0));
auto wnode = new AstNode(AST_WIRE, make_range(offset, 0));
wnode->str = name; wnode->str = name;
wnode->is_logic = true; wnode->is_logic = true;
wnode->range_valid = true; wnode->range_valid = true;
@ -560,6 +545,8 @@ static AstNode *make_packed_struct(AstNode *template_node, std::string &name, de
for (auto &pair : attributes) { for (auto &pair : attributes) {
wnode->set_attribute(pair.first, pair.second->clone()); wnode->set_attribute(pair.first, pair.second->clone());
} }
// resolve packed dimension
while (wnode->simplify(true, 1, -1, false)) {}
// make sure this node is the one in scope for this name // make sure this node is the one in scope for this name
current_scope[name] = wnode; current_scope[name] = wnode;
// add all the struct members to scope under the wire's name // add all the struct members to scope under the wire's name
@ -1986,6 +1973,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
range_swapped = template_node->range_swapped; range_swapped = template_node->range_swapped;
range_left = template_node->range_left; range_left = template_node->range_left;
range_right = template_node->range_right; range_right = template_node->range_right;
set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str));
for (auto template_child : template_node->children) for (auto template_child : template_node->children)
children.push_back(template_child->clone()); children.push_back(template_child->clone());
@ -2046,9 +2034,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
if (old_range_valid != range_valid) if (old_range_valid != range_valid)
did_something = true; did_something = true;
if (range_valid && range_right > range_left) { if (range_valid && range_right > range_left) {
int tmp = range_right; std::swap(range_left, range_right);
range_right = range_left;
range_left = tmp;
range_swapped = true; range_swapped = true;
} }
} }
@ -2093,58 +2079,83 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
} }
} }
// resolve multiranges on memory decl // Resolve packed and unpacked ranges in declarations.
if (type == AST_MEMORY && children.size() > 1 && children[1]->type == AST_MULTIRANGE) if ((type == AST_WIRE || type == AST_MEMORY) && dimensions.empty()) {
{ if (!children.empty()) {
int total_size = 1; // Unpacked ranges first, then packed ranges.
multirange_dimensions.clear(); for (int i = std::min(GetSize(children), 2) - 1; i >= 0; i--) {
multirange_swapped.clear(); if (children[i]->type == AST_MULTIRANGE) {
for (auto range : children[1]->children) { int width = 1;
if (!range->range_valid) for (auto range : children[i]->children) {
input_error("Non-constant range on memory decl.\n"); width *= add_dimension(this, range);
multirange_dimensions.push_back(min(range->range_left, range->range_right)); if (i) unpacked_dimensions++;
multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1); }
multirange_swapped.push_back(range->range_swapped); delete children[i];
total_size *= multirange_dimensions.back(); int left = width - 1, right = 0;
if (i)
std::swap(left, right);
children[i] = new AstNode(AST_RANGE, mkconst_int(left, true), mkconst_int(right, true));
fixup_hierarchy_flags();
did_something = true;
} else if (children[i]->type == AST_RANGE) {
add_dimension(this, children[i]);
if (i) unpacked_dimensions++;
}
}
} else {
// 1 bit signal: bit, logic or reg
dimensions.push_back({ 0, 1, false });
} }
delete children[1];
children[1] = new AstNode(AST_RANGE, AstNode::mkconst_int(0, true), AstNode::mkconst_int(total_size-1, true));
fixup_hierarchy_flags();
did_something = true;
} }
// resolve multiranges on memory access // Resolve multidimensional array access.
if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY && children.size() > 0 && children[0]->type == AST_MULTIRANGE) if (type == AST_IDENTIFIER && !basic_prep && id2ast && (id2ast->type == AST_WIRE || id2ast->type == AST_MEMORY) &&
children.size() > 0 && (children[0]->type == AST_RANGE || children[0]->type == AST_MULTIRANGE))
{ {
AstNode *index_expr = nullptr; int dims_sel = children[0]->type == AST_MULTIRANGE ? children[0]->children.size() : 1;
// Save original number of dimensions for $size() etc.
integer = dims_sel;
integer = children[0]->children.size(); // save original number of dimensions for $size() etc. // Split access into unpacked and packed parts.
for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++) AstNode *unpacked_range = nullptr;
{ AstNode *packed_range = nullptr;
if (GetSize(children[0]->children) <= i)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
AstNode *new_index_expr = children[0]->children[i]->children.at(0)->clone(); if (id2ast->unpacked_dimensions) {
if (id2ast->unpacked_dimensions > 1) {
if (id2ast->multirange_dimensions[2*i]) // Flattened range for access to unpacked dimensions.
new_index_expr = new AstNode(AST_SUB, new_index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i], true)); unpacked_range = make_index_range(id2ast, true);
} else {
if (i == 0) // Index into one-dimensional unpacked part; unlink simple range node.
index_expr = new_index_expr; AstNode *&range = children[0]->type == AST_MULTIRANGE ? children[0]->children[0] : children[0];
else unpacked_range = range;
index_expr = new AstNode(AST_ADD, new AstNode(AST_MUL, index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i+1], true)), new_index_expr); range = nullptr;
}
} }
for (int i = GetSize(id2ast->multirange_dimensions)/2; i < GetSize(children[0]->children); i++) if (dims_sel > id2ast->unpacked_dimensions) {
children.push_back(children[0]->children[i]->clone()); if (GetSize(id2ast->dimensions) - id2ast->unpacked_dimensions > 1) {
// Flattened range for access to packed dimensions.
packed_range = make_index_range(id2ast, false);
} else {
// Index into one-dimensional packed part; unlink simple range node.
AstNode *&range = children[0]->type == AST_MULTIRANGE ? children[0]->children[dims_sel - 1] : children[0];
packed_range = range;
range = nullptr;
}
}
delete children[0]; for (auto &it : children)
if (index_expr == nullptr) delete it;
children.erase(children.begin()); children.clear();
else
children[0] = new AstNode(AST_RANGE, index_expr); if (unpacked_range)
children.push_back(unpacked_range);
if (packed_range)
children.push_back(packed_range);
fixup_hierarchy_flags(); fixup_hierarchy_flags();
basic_prep = true;
did_something = true; did_something = true;
} }
@ -2210,12 +2221,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
if (found_sname) { if (found_sname) {
// structure member, rewrite this node to reference the packed struct wire // structure member, rewrite this node to reference the packed struct wire
auto range = make_struct_member_range(this, item_node); auto range = make_index_range(item_node);
newNode = new AstNode(AST_IDENTIFIER, range); newNode = new AstNode(AST_IDENTIFIER, range);
newNode->str = sname; newNode->str = sname;
// save type and original number of dimensions for $size() etc. // save type and original number of dimensions for $size() etc.
newNode->set_attribute(ID::wiretype, item_node->clone()); newNode->set_attribute(ID::wiretype, item_node->clone());
if (!item_node->multirange_dimensions.empty() && children.size() > 0) { if (!item_node->dimensions.empty() && children.size() > 0) {
if (children[0]->type == AST_RANGE) if (children[0]->type == AST_RANGE)
newNode->integer = 1; newNode->integer = 1;
else if (children[0]->type == AST_MULTIRANGE) else if (children[0]->type == AST_MULTIRANGE)
@ -2835,7 +2846,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
if (!children[0]->id2ast->range_valid) if (!children[0]->id2ast->range_valid)
goto skip_dynamic_range_lvalue_expansion; goto skip_dynamic_range_lvalue_expansion;
AST::AstNode *member_node = get_struct_member(children[0]); AST::AstNode *member_node = children[0]->get_struct_member();
int wire_width = member_node ? int wire_width = member_node ?
member_node->range_left - member_node->range_right + 1 : member_node->range_left - member_node->range_right + 1 :
children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
@ -2881,7 +2892,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
int dims = children[0]->integer; int dims = children[0]->integer;
stride = wire_width; stride = wire_width;
for (int dim = 0; dim < dims; dim++) { for (int dim = 0; dim < dims; dim++) {
stride /= get_struct_range_width(member_node, dim); stride /= member_node->dimensions[dim].range_width;
} }
bitno_div = stride; bitno_div = stride;
} else { } else {
@ -3042,6 +3053,8 @@ skip_dynamic_range_lvalue_expansion:;
// found right-hand side identifier for memory -> replace with memory read port // found right-hand side identifier for memory -> replace with memory read port
if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue && if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue &&
children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) { children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) {
if (integer < (unsigned)id2ast->unpacked_dimensions)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone()); newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone());
newNode->str = str; newNode->str = str;
newNode->id2ast = id2ast; newNode->id2ast = id2ast;
@ -3100,6 +3113,9 @@ skip_dynamic_range_lvalue_expansion:;
children[0]->id2ast->children[0]->range_valid && children[0]->id2ast->children[1]->range_valid && children[0]->id2ast->children[0]->range_valid && children[0]->id2ast->children[1]->range_valid &&
(children[0]->children.size() == 1 || children[0]->children.size() == 2) && children[0]->children[0]->type == AST_RANGE) (children[0]->children.size() == 1 || children[0]->children.size() == 2) && children[0]->children[0]->type == AST_RANGE)
{ {
if (children[0]->integer < (unsigned)children[0]->id2ast->unpacked_dimensions)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
std::stringstream sstr; std::stringstream sstr;
sstr << "$memwr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); sstr << "$memwr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN"; std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN";
@ -3495,8 +3511,6 @@ skip_dynamic_range_lvalue_expansion:;
int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire
AstNode *id_ast = NULL; AstNode *id_ast = NULL;
// Is this needed?
//while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { }
buf->detectSignWidth(width_hint, sign_hint); buf->detectSignWidth(width_hint, sign_hint);
if (buf->type == AST_IDENTIFIER) { if (buf->type == AST_IDENTIFIER) {
@ -3506,99 +3520,27 @@ skip_dynamic_range_lvalue_expansion:;
if (!id_ast) if (!id_ast)
input_error("Failed to resolve identifier %s for width detection!\n", buf->str.c_str()); input_error("Failed to resolve identifier %s for width detection!\n", buf->str.c_str());
// Check for item in packed struct / union if (id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) {
AST::AstNode *item_node = get_struct_member(buf); // Check for item in packed struct / union
if (id_ast->type == AST_WIRE && item_node) { AstNode *item_node = buf->get_struct_member();
if (item_node)
id_ast = item_node;
// The dimension of the original array expression is saved in the 'integer' field // The dimension of the original array expression is saved in the 'integer' field
dim += buf->integer; dim += buf->integer;
if (item_node->multirange_dimensions.empty()) {
if (dim != 1)
input_error("Dimension %d out of range in `%s', as it only has one dimension!\n", dim, item_node->str.c_str());
left = high = item_node->range_left;
right = low = item_node->range_right;
} else {
int dims = GetSize(item_node->multirange_dimensions)/2;
if (dim < 1 || dim > dims)
input_error("Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, item_node->str.c_str(), dims);
right = low = get_struct_range_offset(item_node, dim - 1);
left = high = low + get_struct_range_width(item_node, dim - 1) - 1;
if (item_node->multirange_swapped[dim - 1]) {
std::swap(left, right);
}
for (int i = dim; i < dims; i++) {
mem_depth *= get_struct_range_width(item_node, i);
}
}
}
// Otherwise, we have 4 cases:
// wire x; ==> AST_WIRE, no AST_RANGE children
// wire [1:0]x; ==> AST_WIRE, AST_RANGE children
// wire [1:0]x[1:0]; ==> AST_MEMORY, two AST_RANGE children (1st for packed, 2nd for unpacked)
// wire [1:0]x[1:0][1:0]; ==> AST_MEMORY, one AST_RANGE child (0) for packed, then AST_MULTIRANGE child (1) for unpacked
// (updated: actually by the time we are here, AST_MULTIRANGE is converted into one big AST_RANGE)
// case 0 handled by default
else if ((id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) && id_ast->children.size() > 0) {
// handle packed array left/right for case 1, and cases 2/3 when requesting the last dimension (packed side)
AstNode *wire_range = id_ast->children[0];
left = wire_range->children[0]->integer;
right = wire_range->children[1]->integer;
high = max(left, right);
low = min(left, right);
}
if (id_ast->type == AST_MEMORY) {
// a slice of our identifier means we advance to the next dimension, e.g. $size(a[3])
if (buf->children.size() > 0) {
// something is hanging below this identifier
if (buf->children[0]->type == AST_RANGE && buf->integer == 0)
// if integer == 0, this node was originally created as AST_RANGE so it's dimension is 1
dim++;
// more than one range, e.g. $size(a[3][2])
else // created an AST_MULTIRANGE, converted to AST_RANGE, but original dimension saved in 'integer' field
dim += buf->integer; // increment by multirange size
}
// We got here only if the argument is a memory int dims = GetSize(id_ast->dimensions);
// Otherwise $size() and $bits() return the expression width // TODO: IEEE Std 1800-2017 20.7: "If the first argument to an array query function would cause $dimensions to return 0
AstNode *mem_range = id_ast->children[1]; // or if the second argument is out of range, then 'x shall be returned."
if (str == "\\$bits") { if (dim < 1 || dim > dims)
if (mem_range->type == AST_RANGE) { input_error("Dimension %d out of range in `%s', as it only has %d dimensions!\n", dim, id_ast->str.c_str(), dims);
if (!mem_range->range_valid) right = low = id_ast->dimensions[dim - 1].range_right;
input_error("Failed to detect width of memory access `%s'!\n", buf->str.c_str()); left = high = low + id_ast->dimensions[dim - 1].range_width - 1;
mem_depth = mem_range->range_left - mem_range->range_right + 1; if (id_ast->dimensions[dim - 1].range_swapped) {
} else std::swap(left, right);
input_error("Unknown memory depth AST type in `%s'!\n", buf->str.c_str()); }
} else { for (int i = dim; i < dims; i++) {
// $size(), $left(), $right(), $high(), $low() mem_depth *= id_ast->dimensions[i].range_width;
int dims = 1;
if (mem_range->type == AST_RANGE) {
if (id_ast->multirange_dimensions.empty()) {
if (!mem_range->range_valid)
input_error("Failed to detect width of memory access `%s'!\n", buf->str.c_str());
if (dim == 1) {
left = mem_range->range_right;
right = mem_range->range_left;
high = max(left, right);
low = min(left, right);
}
} else {
dims = GetSize(id_ast->multirange_dimensions)/2;
if (dim <= dims) {
width_hint = id_ast->multirange_dimensions[2*dim-1];
high = id_ast->multirange_dimensions[2*dim-2] + id_ast->multirange_dimensions[2*dim-1] - 1;
low = id_ast->multirange_dimensions[2*dim-2];
if (id_ast->multirange_swapped[dim-1]) {
left = low;
right = high;
} else {
right = low;
left = high;
}
} else if ((dim > dims+1) || (dim < 0))
input_error("Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
}
} else {
input_error("Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
}
} }
} }
width = high - low + 1; width = high - low + 1;
@ -4180,7 +4122,7 @@ replace_fcall_later:;
tmp_range_left = (param_width + 2*param_offset) - children[0]->range_right - 1; tmp_range_left = (param_width + 2*param_offset) - children[0]->range_right - 1;
tmp_range_right = (param_width + 2*param_offset) - children[0]->range_left - 1; tmp_range_right = (param_width + 2*param_offset) - children[0]->range_left - 1;
} }
AST::AstNode *member_node = get_struct_member(this); AstNode *member_node = get_struct_member();
int chunk_offset = member_node ? member_node->range_right : 0; int chunk_offset = member_node ? member_node->range_right : 0;
log_assert(!(chunk_offset && param_upto)); log_assert(!(chunk_offset && param_upto));
for (int i = tmp_range_right; i <= tmp_range_left; i++) { for (int i = tmp_range_right; i <= tmp_range_left; i++) {
@ -4820,6 +4762,9 @@ void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg
{ {
AstNode *mem = id2ast; AstNode *mem = id2ast;
if (integer < (unsigned)mem->unpacked_dimensions)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
// flag if used after blocking assignment (in same proc) // flag if used after blocking assignment (in same proc)
if ((proc_flags[mem] & AstNode::MEM2REG_FL_EQ1) && !(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_EQ2)) { if ((proc_flags[mem] & AstNode::MEM2REG_FL_EQ1) && !(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_EQ2)) {
mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line));
@ -5103,7 +5048,7 @@ bool AstNode::mem2reg_as_needed_pass2(pool<AstNode*> &mem2reg_set, AstNode *mod,
int width; int width;
if (bit_part_sel) if (bit_part_sel)
{ {
bit_part_sel->dumpAst(nullptr, "? "); // bit_part_sel->dumpAst(nullptr, "? ");
if (bit_part_sel->children.size() == 1) if (bit_part_sel->children.size() == 1)
width = 0; width = 0;
else else

View File

@ -197,9 +197,20 @@ static AstNode *checkRange(AstNode *type_node, AstNode *range_node)
range_node = makeRange(type_node->range_left, type_node->range_right, false); range_node = makeRange(type_node->range_left, type_node->range_right, false);
} }
} }
if (range_node && range_node->children.size() != 2) {
frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]"); if (range_node) {
bool valid = true;
if (range_node->type == AST_RANGE) {
valid = range_node->children.size() == 2;
} else { // AST_MULTIRANGE
for (auto child : range_node->children) {
valid = valid && child->children.size() == 2;
}
}
if (!valid)
frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form [<expr>:<expr>]");
} }
return range_node; return range_node;
} }
@ -672,7 +683,7 @@ module_arg:
ast_stack.back()->children.push_back(astbuf2); ast_stack.back()->children.push_back(astbuf2);
delete astbuf1; // really only needed if multiple instances of same type. delete astbuf1; // really only needed if multiple instances of same type.
} module_arg_opt_assignment | } module_arg_opt_assignment |
attr wire_type range TOK_ID { attr wire_type range_or_multirange TOK_ID {
AstNode *node = $2; AstNode *node = $2;
node->str = *$4; node->str = *$4;
SET_AST_NODE_LOC(node, @4, @4); SET_AST_NODE_LOC(node, @4, @4);
@ -1165,7 +1176,7 @@ task_func_args:
task_func_port | task_func_args ',' task_func_port; task_func_port | task_func_args ',' task_func_port;
task_func_port: task_func_port:
attr wire_type range { attr wire_type range_or_multirange {
bool prev_was_input = true; bool prev_was_input = true;
bool prev_was_output = false; bool prev_was_output = false;
if (albuf) { if (albuf) {
@ -1928,7 +1939,7 @@ struct_var: TOK_ID { auto *var_node = astbuf2->clone();
///////// /////////
wire_decl: wire_decl:
attr wire_type range { attr wire_type range_or_multirange {
albuf = $1; albuf = $1;
astbuf1 = $2; astbuf1 = $2;
astbuf2 = checkRange(astbuf1, $3); astbuf2 = checkRange(astbuf1, $3);
@ -2104,7 +2115,7 @@ type_name: TOK_ID // first time seen
; ;
typedef_decl: typedef_decl:
TOK_TYPEDEF typedef_base_type range type_name range_or_multirange ';' { TOK_TYPEDEF typedef_base_type range_or_multirange type_name range_or_multirange ';' {
astbuf1 = $2; astbuf1 = $2;
astbuf2 = checkRange(astbuf1, $3); astbuf2 = checkRange(astbuf1, $3);
if (astbuf2) if (astbuf2)

View File

@ -16,17 +16,17 @@ assert property ($size({3{x}}) == 3*4);
assert property ($size(y) == 6); assert property ($size(y) == 6);
assert property ($size(y, 1) == 6); assert property ($size(y, 1) == 6);
assert property ($size(y, (1+1)) == 4); assert property ($size(y, (1+1)) == 4);
assert property ($size(y[2], 1) == 4);
// This is unsupported at the moment // This is unsupported at the moment
//assert property ($size(y[2], 1) == 4);
//assert property ($size(y[2][1], 1) == 1); //assert property ($size(y[2][1], 1) == 1);
assert property ($size(z) == 6); assert property ($size(z) == 6);
assert property ($size(z, 1) == 6); assert property ($size(z, 1) == 6);
assert property ($size(z, 2) == 8); assert property ($size(z, 2) == 8);
assert property ($size(z, 3) == 4); assert property ($size(z, 3) == 4);
// This is unsupported at the moment
assert property ($size(z[3], 1) == 8); assert property ($size(z[3], 1) == 8);
assert property ($size(z[3][3], 1) == 4); assert property ($size(z[3][3], 1) == 4);
// This is unsupported at the moment
//assert property ($size(z[3][3][3], 1) == 1); //assert property ($size(z[3][3][3], 1) == 1);
// This should trigger an error if enabled (it does). // This should trigger an error if enabled (it does).
//assert property ($size(z, 4) == 4); //assert property ($size(z, 4) == 4);

View File

@ -8,9 +8,21 @@ module top;
logic a [3]; logic a [3];
logic b [3][5]; logic b [3][5];
logic c [3][5][7]; logic c [3][5][7];
logic [2:0] d;
logic [2:0][4:0] e;
logic [2:0][4:0][6:0] f;
logic [2:0] g [3];
logic [2:0][4:0] h [3][5];
logic [2:0][4:0][6:0] i [3][5][7];
`STATIC_ASSERT($bits(a) == 3); `STATIC_ASSERT($bits(a) == 3);
`STATIC_ASSERT($bits(b) == 15); `STATIC_ASSERT($bits(b) == 15);
`STATIC_ASSERT($bits(c) == 105); `STATIC_ASSERT($bits(c) == 105);
`STATIC_ASSERT($bits(d) == 3);
`STATIC_ASSERT($bits(e) == 15);
`STATIC_ASSERT($bits(f) == 105);
`STATIC_ASSERT($bits(g) == 9);
`STATIC_ASSERT($bits(h) == 225);
`STATIC_ASSERT($bits(i) == 11025);
endmodule endmodule