Support nested struct l-values.

This commit is contained in:
Stephen Williams 2019-09-16 13:50:17 -07:00
parent b639c4c9aa
commit 1c281c2d77
2 changed files with 287 additions and 217 deletions

View File

@ -433,10 +433,10 @@ class PEIdent : public PExpr {
bool need_const_idx) const;
NetAssign_*elaborate_lval_net_class_member_(Design*, NetScope*,
NetNet*,
const perm_string&) const;
pform_name_t) const;
bool elaborate_lval_net_packed_member_(Design*, NetScope*,
NetAssign_*,
const name_component_t&) const;
pform_name_t member_path) const;
bool elaborate_lval_darray_bit_(Design*, NetScope*,
NetAssign_*) const;
@ -530,8 +530,7 @@ class PEIdent : public PExpr {
NetNet* elaborate_lnet_common_(Design*des, NetScope*scope,
bool bidirectional_flag) const;
NetAssign_*scan_lname_for_nested_members_(Design*des, NetScope*scope,
const pform_name_t&path) const;
bool eval_part_select_(Design*des, NetScope*scope, NetNet*sig,
long&midx, long&lidx) const;
};

View File

@ -146,57 +146,6 @@ NetAssign_* PEConcat::elaborate_lval(Design*des,
return res;
}
NetAssign_*PEIdent::scan_lname_for_nested_members_(Design*des, NetScope*scope,
const pform_name_t&cur_path) const
{
if (cur_path.size() == 1)
return 0;
pform_name_t use_path = cur_path;
name_component_t tail = use_path.back();
use_path.pop_back();
NetNet* reg = 0;
const NetExpr*par = 0;
NetEvent* eve = 0;
symbol_search(this, des, scope, use_path, reg, par, eve);
if (reg == 0) {
NetAssign_*tmp = scan_lname_for_nested_members_(des, scope, use_path);
if (tmp == 0)
return 0;
tmp = new NetAssign_(tmp);
tmp->set_property(tail.name);
return tmp;
}
if (reg->struct_type() && reg->struct_type()->packed()) {
NetAssign_*tmp = new NetAssign_(reg);
elaborate_lval_net_packed_member_(des, scope, tmp, tail);
return tmp;
}
#if 0
if (reg->struct_type() && reg->struct_type()->packed()) {
cerr << get_fileline() << ": sorry: "
<< "I don't know what to do with packed struct " << use_path
<< " with member " << tail << "." << endl;
return 0;
}
#endif
if (reg->struct_type() && !reg->struct_type()->packed()) {
cerr << get_fileline() << ": sorry: "
<< "I don't know what to do with unpacked struct " << use_path
<< " with member " << tail << "." << endl;
return 0;
}
if (reg->class_type()) {
return elaborate_lval_net_class_member_(des, scope, reg, tail.name);
}
return 0;
}
/*
* Handle the ident as an l-value. This includes bit and part selects
@ -210,7 +159,6 @@ NetAssign_* PEIdent::elaborate_lval(Design*des,
NetNet* reg = 0;
const NetExpr*par = 0;
NetEvent* eve = 0;
perm_string method_name;
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval: "
@ -231,32 +179,28 @@ NetAssign_* PEIdent::elaborate_lval(Design*des,
ivl_assert(*this, use_scope);
}
symbol_search(this, des, use_scope, path_, reg, par, eve);
/* If the signal is not found, check to see if this is a
member of a struct. Take the name of the form "a.b.member",
remove the member and store it into method_name, and retry
the search with "a.b". */
if (reg == 0 && path_.size() >= 2) {
pform_name_t use_path = path_;
perm_string tmp_name = peek_tail_name(use_path);
use_path.pop_back();
symbol_search(this, des, use_scope, use_path, reg, par, eve);
if (reg && reg->struct_type()) {
method_name = tmp_name;
} else if (reg && reg->class_type()) {
method_name = tmp_name;
} else if (NetAssign_*subl = scan_lname_for_nested_members_(des, use_scope, path_)) {
return subl;
} else {
reg = 0;
}
/* Try to find the base part of the path that names the
variable. The remainer is the member path. For example, if
the path is a.b.c.d, and a.b is the path to a variable,
then a.b becomes the base_path and c.d becomes the
member_path. If we cannot find the variable with any
prefix, then the base_path will be empty after this loop
and reg will remain nil. */
pform_name_t base_path = path_;
pform_name_t member_path;
while (reg == 0 && base_path.size() > 0) {
symbol_search(this, des, use_scope, base_path, reg, par, eve);
// Found it!
if (reg != 0) break;
// Not found. Try to pop another name off the base_path
// and push it to the front of the member_path.
member_path.push_front( base_path.back() );
base_path.pop_back();
}
/* The l-value must be a variable. If not, then give up and
print a useful error message. */
if (reg == 0) {
if (use_scope->type()==NetScope::FUNC
&& use_scope->func_def()->return_sig()==0
@ -278,7 +222,10 @@ NetAssign_* PEIdent::elaborate_lval(Design*des,
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval: "
<< "Found l-value as reg."
<< "Found l-value path_=" << path_
<< " as reg=" << reg->name()
<< " base_path=" << base_path
<< ", member_path=" << member_path
<< " unpacked_dimensions()=" << reg->unpacked_dimensions() << endl;
}
@ -324,21 +271,27 @@ NetAssign_* PEIdent::elaborate_lval(Design*des,
return 0;
}
if (reg->struct_type() && !method_name.nil()) {
// If we find that the matched variable is a packed struct,
// then we can handled it with the net_packed_member_ method.
if (reg->struct_type() && member_path.size() > 0) {
NetAssign_*lv = new NetAssign_(reg);
name_component_t tmp_name (method_name);
elaborate_lval_net_packed_member_(des, use_scope, lv, tmp_name);
elaborate_lval_net_packed_member_(des, use_scope, lv, member_path);
return lv;
}
if (reg->class_type() && !method_name.nil() && gn_system_verilog()) {
NetAssign_*lv = elaborate_lval_net_class_member_(des, use_scope, reg, method_name);
// If the variable is a class object, then handle it with the
// net_class_member_ method.
if (reg->class_type() && member_path.size() > 0 && gn_system_verilog()) {
NetAssign_*lv = elaborate_lval_net_class_member_(des, use_scope, reg, member_path);
return lv;
}
// Past this point, we should have taken care of the cases
// where the name is a member/method of a struct/class.
ivl_assert(*this, method_name.nil());
// XXXX ivl_assert(*this, method_name.nil());
ivl_assert(*this, member_path.size() == 0);
bool need_const_idx = is_cassign || is_force || (reg->type()==NetNet::UNRESOLVED_WIRE);
@ -1063,89 +1016,137 @@ bool PEIdent::elaborate_lval_net_idx_(Design*des,
return true;
}
/*
* When the l-value turns out to be a class object, this method is
* called with the bound variable, and the method path. For example,
* if path_=a.b.c and a.b binds to the variable, then sig is b, and
* member_path=c. if path_=obj.base.x, and base_path=obj, then sig is
* obj, and member_path=base.x.
*/
NetAssign_* PEIdent::elaborate_lval_net_class_member_(Design*des, NetScope*scope,
NetNet*sig, const perm_string&method_name) const
NetNet*sig, pform_name_t member_path) const
{
if (debug_elaborate) {
cerr << get_fileline() << ": elaborate_lval_net_class_member_: "
<< "l-value is property " << method_name
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_class_member_: "
<< "l-value is property " << member_path
<< " of " << sig->name() << "." << endl;
}
const netclass_t*class_type = sig->class_type();
ivl_assert(*this, class_type);
/* Make sure the property is really present in the class. If
not, then generate an error message and return an error. */
int pidx = class_type->property_idx_from_name(method_name);
if (pidx < 0) {
cerr << get_fileline() << ": error: Class " << class_type->get_name()
<< " does not have a property " << method_name << "." << endl;
des->errors += 1;
return 0;
}
// Iterate over the member_path. This handles nested class
// object, by generating nested NetAssign_ object. We start
// with lv==0, so the front of the member_path is the member
// of the outermost class. This generates an lv from sig. Then
// iterate over the remaining of the member_path, replacing
// the outer lv with an lv that nests the lv from the previous
// iteration.
NetAssign_*lv = 0;
do {
// Start with the first component of the member path...
perm_string method_name = peek_head_name(member_path);
// Pull that component from the member_path. We need to
// know the current member being worked on, and will
// need to know if there are more members to be worked on.
name_component_t member_cur = member_path.front();
member_path.pop_front();
property_qualifier_t qual = class_type->get_prop_qual(pidx);
if (qual.test_local() && ! class_type->test_scope_is_method(scope)) {
cerr << get_fileline() << ": error: "
<< "Local property " << class_type->get_prop_name(pidx)
<< " is not accessible (l-value) in this context."
<< " (scope=" << scope_path(scope) << ")" << endl;
des->errors += 1;
} else if (qual.test_static()) {
// Special case: this is a static property. Ignore the
// "this" sig and use the property itself, which is not
// part of the sig, as the l-value.
NetNet*psig = class_type->find_static_property(method_name);
ivl_assert(*this, psig);
NetAssign_*lv = new NetAssign_(psig);
return lv;
} else if (qual.test_const()) {
cerr << get_fileline() << ": error: "
<< "Property " << class_type->get_prop_name(pidx)
<< " is constant in this context." << endl;
des->errors += 1;
}
NetAssign_*lv = new NetAssign_(sig);
lv->set_property(method_name);
ivl_type_t ptype = class_type->get_prop_type(pidx);
const netdarray_t*mtype = dynamic_cast<const netdarray_t*> (ptype);
if (mtype) {
const name_component_t&name_tail = path_.back();
if (! name_tail.index.empty()) {
cerr << get_fileline() << ": sorry: "
<< "Array index of array properties not supported."
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_class_member_: "
<< "Processing member_cur=" << member_cur
<< endl;
}
// Make sure the property is really present in the class. If
// not, then generate an error message and return an error.
int pidx = class_type->property_idx_from_name(method_name);
if (pidx < 0) {
cerr << get_fileline() << ": error: Class " << class_type->get_name()
<< " does not have a property " << method_name << "." << endl;
des->errors += 1;
return 0;
}
property_qualifier_t qual = class_type->get_prop_qual(pidx);
if (qual.test_local() && ! class_type->test_scope_is_method(scope)) {
cerr << get_fileline() << ": error: "
<< "Local property " << class_type->get_prop_name(pidx)
<< " is not accessible (l-value) in this context."
<< " (scope=" << scope_path(scope) << ")" << endl;
des->errors += 1;
} else if (qual.test_static()) {
// Special case: this is a static property. Ignore the
// "this" sig and use the property itself, which is not
// part of the sig, as the l-value.
NetNet*psig = class_type->find_static_property(method_name);
ivl_assert(*this, psig);
lv = new NetAssign_(psig);
return lv;
} else if (qual.test_const()) {
cerr << get_fileline() << ": error: "
<< "Property " << class_type->get_prop_name(pidx)
<< " is constant in this context." << endl;
des->errors += 1;
}
}
lv = lv? new NetAssign_(lv) : new NetAssign_(sig);
lv->set_property(method_name);
// Now get the type of the property.
ivl_type_t ptype = class_type->get_prop_type(pidx);
const netdarray_t*mtype = dynamic_cast<const netdarray_t*> (ptype);
if (mtype) {
if (! member_cur.index.empty()) {
cerr << get_fileline() << ": sorry: "
<< "Array index of array properties not supported."
<< endl;
des->errors += 1;
}
}
// If the current member is a class object, then get the
// type. We may wind up iterating, and need the proper
// class type.
class_type = dynamic_cast<const netclass_t*>(ptype);
} while (member_path.size() > 0);
return lv;
}
/*
* This method is caled to handle l-value identifiers that are packed
* structs. The lv is already attached to the variable, so this method
* calculates the part select that is defined by the member_path. For
* example, if the path_ is main.sub.sub_local, and the variable is
* main, then we know at this point that main is a packed struct, and
* lv contains the reference to the bound variable (main). In this
* case member_path==sub.sub_local, and it is up to this method to
* work out the part select that the member_path represents.
*/
bool PEIdent::elaborate_lval_net_packed_member_(Design*des, NetScope*scope,
NetAssign_*lv,
const name_component_t&member_comp) const
pform_name_t member_path) const
{
const perm_string&member_name = member_comp.name;
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_packed_member_: "
<< "path_=" << path_
<< " member_path=" << member_path
<< endl;
}
NetNet*reg = lv->sig();
ivl_assert(*this, reg);
const netstruct_t*struct_type = reg->struct_type();
ivl_assert(*this, struct_type);
if (debug_elaborate) {
cerr << get_fileline() << ": debug: elaborate lval packed member: "
<< "path_=" << path_ << endl;
}
if (! struct_type->packed()) {
cerr << get_fileline() << ": sorry: Only packed structures "
<< "are supported in l-value." << endl;
@ -1153,99 +1154,169 @@ bool PEIdent::elaborate_lval_net_packed_member_(Design*des, NetScope*scope,
return false;
}
// Shouldn't be seeing unpacked arrays of packed structs...
ivl_assert(*this, reg->unpacked_dimensions() == 0);
// This is a packed member, so the name is of the form
// "a.b[...].c[...]" which means that the path_ must have at
// least 2 components. We are processing "c[...]" at that
// point (otherwise known as member_name) so we'll save a
// reference to it in name_tail. We are also processing "b[]"
// so save that as name_base.
ivl_assert(*this, path_.size() >= 2);
// Looking for the base name. We need that to know about
// indices we may need to apply. This is to handle the case
// that the base is an array of structs, and not just a
// struct.
pform_name_t::const_reverse_iterator name_idx = path_.rbegin();
ivl_assert(*this, name_idx->name == member_name);
const name_component_t&name_tail = *name_idx;
for (size_t idx = 1 ; idx < member_path.size() ; idx += 1)
++ name_idx;
if (name_idx->name != peek_head_name(member_path)) {
cerr << get_fileline() << ": internal error: "
<< "name_idx=" << name_idx->name
<< ", expecting member_name=" << peek_head_name(member_path)
<< endl;
des->errors += 1;
return false;
}
ivl_assert(*this, name_idx->name == peek_head_name(member_path));
++ name_idx;
const name_component_t&name_base = *name_idx;
// Calculate the offset within the packed structure of the
// member, and any indices. We will add in the offset of the
// struct into the packed array later. Note that this works
// for packed unions as well (although the offset will be 0
// for union members).
unsigned long off;
const netstruct_t::member_t* member = struct_type->packed_member(member_name, off);
// Shouldn't be seeing unpacked arrays of packed structs...
ivl_assert(*this, reg->unpacked_dimensions() == 0);
if (member == 0) {
cerr << get_fileline() << ": error: Member " << member_name
<< " is not a member of variable " << reg->name() << endl;
des->errors += 1;
return false;
}
// These make up the "part" select that is the equivilent of
// following the member path through the nested structs. To
// start with, the off[set] is zero, and use_width is the
// width of the entire variable. The first member_comp is at
// some offset within the variable, and will have a reduced
// width. As we step through the member_path the off
// increases, and use_width shrinks.
unsigned long off = 0;
unsigned long use_width = struct_type->packed_width();
unsigned long use_width = member->net_type->packed_width();
pform_name_t completed_path;
do {
const name_component_t member_comp = member_path.front();
const perm_string&member_name = member_comp.name;
// Get the index component type. At this point, we only
// support bit select or none.
index_component_t::ctype_t use_sel = index_component_t::SEL_NONE;
if (!name_tail.index.empty())
use_sel = name_tail.index.back().sel;
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_packed_member_: "
<< "Processing member_comp=" << member_comp
<< " (completed_path=" << completed_path << ")"
<< endl;
}
if (use_sel != index_component_t::SEL_NONE && use_sel != index_component_t::SEL_BIT) {
cerr << get_fileline() << ": sorry: Assignments to part selects of "
"a struct member are not yet supported." << endl;
des->errors += 1;
return false;
}
// This is a packed member, so the name is of the form
// "a.b[...].c[...]" which means that the path_ must have at
// least 2 components. We are processing "c[...]" at that
// point (otherwise known as member_name) and we have a
// reference to it in member_comp.
if (! name_tail.index.empty()) {
// The member_path is the members we want to follow for the
// variable. For example, main[N].a.b may have main[N] as the
// base_name, and member_path=a.b. The member_name is the
// start of the member_path, and is "a". The member_name
// should be a member of the struct_type type.
// If there are index expressions in this l-value
// expression, then the implicit assumption is that the
// member is a vector type with packed dimensions. For
// example, if the l-value expression is "foo.member[1][2]",
// then the member should be something like:
// ... logic [h:l][m:n] foo;
// Get the dimensions from the netvector_t that this implies.
const netvector_t*mem_vec = dynamic_cast<const netvector_t*>(member->net_type);
ivl_assert(*this, mem_vec);
const vector<netrange_t>&mem_packed_dims = mem_vec->packed_dims();
// Calculate the offset within the packed structure of the
// member, and any indices. We will add in the offset of the
// struct into the packed array later. Note that this works
// for packed unions as well (although the offset will be 0
// for union members).
unsigned long tmp_off;
const netstruct_t::member_t* member = struct_type->packed_member(member_name, tmp_off);
if (name_tail.index.size() > mem_packed_dims.size()) {
cerr << get_fileline() << ": error: "
<< "Too many index expressions for member." << endl;
if (member == 0) {
cerr << get_fileline() << ": error: Member " << member_name
<< " is not a member of struct type of "
<< reg->name()
<< "." << completed_path << endl;
des->errors += 1;
return false;
}
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_packed_member_: "
<< "Member type: " << *(member->net_type)
<< endl;
}
off += tmp_off;
ivl_assert(*this, use_width >= (unsigned long)member->net_type->packed_width());
use_width = member->net_type->packed_width();
// At this point, off and use_width are the part select
// expressed by the member_comp, which is a member of the
// struct. We can further refine the part select with any
// indices that might be present.
// Get the index component type. At this point, we only
// support bit select or none.
index_component_t::ctype_t use_sel = index_component_t::SEL_NONE;
if (!member_comp.index.empty())
use_sel = member_comp.index.back().sel;
if (use_sel != index_component_t::SEL_NONE && use_sel != index_component_t::SEL_BIT) {
cerr << get_fileline() << ": sorry: Assignments to part selects of "
"a struct member are not yet supported." << endl;
des->errors += 1;
return false;
}
// Evaluate all but the last index expression, into prefix_indices.
list<long>prefix_indices;
bool rc = evaluate_index_prefix(des, scope, prefix_indices, name_tail.index);
ivl_assert(*this, rc);
if (! member_comp.index.empty()) {
// Evaluate the last index expression into a constant long.
NetExpr*texpr = elab_and_eval(des, scope, name_tail.index.back().msb, -1, true);
long tmp;
if (texpr == 0 || !eval_as_long(tmp, texpr)) {
cerr << get_fileline() << ": error: "
"Array index expressions must be constant here." << endl;
des->errors += 1;
return false;
// If there are index expressions in this l-value
// expression, then the implicit assumption is that the
// member is a vector type with packed dimensions. For
// example, if the l-value expression is "foo.member[1][2]",
// then the member should be something like:
// ... logic [h:l][m:n] foo;
// Get the dimensions from the netvector_t that this implies.
const netvector_t*mem_vec = dynamic_cast<const netvector_t*>(member->net_type);
ivl_assert(*this, mem_vec);
const vector<netrange_t>&mem_packed_dims = mem_vec->packed_dims();
if (member_comp.index.size() > mem_packed_dims.size()) {
cerr << get_fileline() << ": error: "
<< "Too many index expressions for member." << endl;
des->errors += 1;
return false;
}
// Evaluate all but the last index expression, into prefix_indices.
list<long>prefix_indices;
bool rc = evaluate_index_prefix(des, scope, prefix_indices, member_comp.index);
ivl_assert(*this, rc);
// Evaluate the last index expression into a constant long.
NetExpr*texpr = elab_and_eval(des, scope, member_comp.index.back().msb, -1, true);
long tmp;
if (texpr == 0 || !eval_as_long(tmp, texpr)) {
cerr << get_fileline() << ": error: "
"Array index expressions must be constant here." << endl;
des->errors += 1;
return false;
}
delete texpr;
// Now use the prefix_to_slice function to calculate the
// offset and width of the addressed slice of the member.
long loff;
unsigned long lwid;
prefix_to_slice(mem_packed_dims, prefix_indices, tmp, loff, lwid);
off += loff;
use_width = lwid;
}
delete texpr;
// Complete this component of the path, mark it
// completed, and set up for the next component.
struct_type = dynamic_cast<const netstruct_t*> (member->net_type);
completed_path .push_back(member_comp);
member_path.pop_front();
// Now use the prefix_to_slice function to calculate the
// offset and width of the addressed slice of the member.
long loff;
unsigned long lwid;
prefix_to_slice(mem_packed_dims, prefix_indices, tmp, loff, lwid);
} while (member_path.size() > 0 && struct_type != 0);
off += loff;
use_width = lwid;
if (debug_elaborate) {
cerr << get_fileline() << ": PEIdent::elaborate_lval_net_packed_member_: "
<< "After processing member_path, "
<< "off=" << off
<< ", use_width=" << use_width
<< ", completed_path=" << completed_path
<< ", member_path=" << member_path
<< endl;
}
// The dimensions in the expression must match the packed