verilog: significant block scoping improvements
[yosys.git] / frontends / ast / simplify.cc
index 467afdd8717bdb913cd98cb622cd7c594c5e84a2..77911e96620b23742e8f05997246c8a4fb836105 100644 (file)
@@ -89,9 +89,9 @@ std::string AstNode::process_format_str(const std::string &sformat, int next_arg
                                case 'S':
                                case 'd':
                                case 'D':
-                                       if (got_len)
+                                       if (got_len && len_value != 0)
                                                goto unsupported_format;
-                                       /* fall through */
+                                       YS_FALLTHROUGH
                                case 'x':
                                case 'X':
                                        if (next_arg >= GetSize(children))
@@ -110,6 +110,12 @@ std::string AstNode::process_format_str(const std::string &sformat, int next_arg
                                                goto unsupported_format;
                                        break;
 
+                               case 'l':
+                               case 'L':
+                                       if (got_len)
+                                               goto unsupported_format;
+                                       break;
+
                                default:
                                unsupported_format:
                                        log_file_error(filename, location.first_line, "System task `%s' called with invalid/unsupported format specifier.\n", str.c_str());
@@ -155,6 +161,11 @@ std::string AstNode::process_format_str(const std::string &sformat, int next_arg
                                        sout += log_id(current_module->name);
                                        break;
 
+                               case 'l':
+                               case 'L':
+                                       sout += log_id(current_module->name);
+                                       break;
+
                                default:
                                        log_abort();
                        }
@@ -168,6 +179,386 @@ std::string AstNode::process_format_str(const std::string &sformat, int next_arg
 }
 
 
+void AstNode::annotateTypedEnums(AstNode *template_node)
+{
+       //check if enum
+       if (template_node->attributes.count(ID::enum_type)) {
+               //get reference to enum node:
+               std::string enum_type = template_node->attributes[ID::enum_type]->str.c_str();
+               //                      log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type));
+               //                      log("current scope:\n");
+               //                      for (auto &it : current_scope)
+               //                              log("  %s\n", it.first.c_str());
+               log_assert(current_scope.count(enum_type) == 1);
+               AstNode *enum_node = current_scope.at(enum_type);
+               log_assert(enum_node->type == AST_ENUM);
+               //get width from 1st enum item:
+               log_assert(enum_node->children.size() >= 1);
+               AstNode *enum_item0 = enum_node->children[0];
+               log_assert(enum_item0->type == AST_ENUM_ITEM);
+               int width;
+               if (!enum_item0->range_valid)
+                       width = 1;
+               else if (enum_item0->range_swapped)
+                       width = enum_item0->range_right - enum_item0->range_left + 1;
+               else
+                       width = enum_item0->range_left - enum_item0->range_right + 1;
+               log_assert(width > 0);
+               //add declared enum items:
+               for (auto enum_item : enum_node->children){
+                       log_assert(enum_item->type == AST_ENUM_ITEM);
+                       //get is_signed
+                       bool is_signed;
+                       if (enum_item->children.size() == 1){
+                               is_signed = false;
+                       } else if (enum_item->children.size() == 2){
+                               log_assert(enum_item->children[1]->type == AST_RANGE);
+                               is_signed = enum_item->children[1]->is_signed;
+                       } else {
+                               log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n",
+                                                 enum_item->children.size(),
+                                                 enum_item->str.c_str(), enum_node->str.c_str()
+                               );
+                       }
+                       //start building attribute string
+                       std::string enum_item_str = "\\enum_value_";
+                       //get enum item value
+                       if(enum_item->children[0]->type != AST_CONSTANT){
+                               log_error("expected const, got %s for %s (%s)\n",
+                                                 type2str(enum_item->children[0]->type).c_str(),
+                                                 enum_item->str.c_str(), enum_node->str.c_str()
+                                               );
+                       }
+                       RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed);
+                       enum_item_str.append(val.as_string());
+                       //set attribute for available val to enum item name mappings
+                       attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str);
+               }
+       }
+}
+
+static bool name_has_dot(const std::string &name, std::string &struct_name)
+{
+       // check if plausible struct member name \sss.mmm
+       std::string::size_type pos;
+       if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) {
+               struct_name = name.substr(0, pos);
+               return true;
+       }
+       return false;
+}
+
+static AstNode *make_range(int left, int right, bool is_signed = false)
+{
+       // generate a pre-validated range node for a fixed signal range.
+       auto range = new AstNode(AST_RANGE);
+       range->range_left = left;
+       range->range_right = right;
+       range->range_valid = true;
+       range->children.push_back(AstNode::mkconst_int(left, true));
+       range->children.push_back(AstNode::mkconst_int(right, true));
+       range->is_signed = is_signed;
+       return range;
+}
+
+static int range_width(AstNode *node, AstNode *rnode)
+{
+       log_assert(rnode->type==AST_RANGE);
+       if (!rnode->range_valid) {
+               log_file_error(node->filename, node->location.first_line, "Size must be constant in packed struct/union member %s\n", node->str.c_str());
+
+       }
+       // note: range swapping has already been checked for
+       return rnode->range_left - rnode->range_right + 1;
+}
+
+[[noreturn]] static void struct_array_packing_error(AstNode *node)
+{
+       log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", node->str.c_str());
+}
+
+static void save_struct_array_width(AstNode *node, int width)
+{
+       // stash the stride for the array
+       node->multirange_dimensions.push_back(width);
+
+}
+
+static int get_struct_array_width(AstNode *node)
+{
+       // the stride for the array, 1 if not an array
+       return (node->multirange_dimensions.empty() ? 1 : node->multirange_dimensions.back());
+
+}
+
+static int size_packed_struct(AstNode *snode, int base_offset)
+{
+       // Struct members will be laid out in the structure contiguously from left to right.
+       // Union members all have zero offset from the start of the union.
+       // Determine total packed size and assign offsets.  Store these in the member node.
+       bool is_union = (snode->type == AST_UNION);
+       int offset = 0;
+       int packed_width = -1;
+       // examine members from last to first
+       for (auto it = snode->children.rbegin(); it != snode->children.rend(); ++it) {
+               auto node = *it;
+               int width;
+               if (node->type == AST_STRUCT || node->type == AST_UNION) {
+                       // embedded struct or union
+                       width = size_packed_struct(node, base_offset + offset);
+               }
+               else {
+                       log_assert(node->type == AST_STRUCT_ITEM);
+                       if (node->children.size() > 0 && node->children[0]->type == AST_RANGE) {
+                               // member width e.g. bit [7:0] a
+                               width = range_width(node, node->children[0]);
+                               if (node->children.size() == 2) {
+                                       if (node->children[1]->type == AST_RANGE) {
+                                               // unpacked array e.g. bit [63:0] a [0:3]
+                                               auto rnode = node->children[1];
+                                               int array_count = range_width(node, rnode);
+                                               if (array_count == 1) {
+                                                       // C-type array size e.g. bit [63:0] a [4]
+                                                       array_count = rnode->range_left;
+                                               }
+                                               save_struct_array_width(node, width);
+                                               width *= array_count;
+                                       }
+                                       else {
+                                               // array element must be single bit for a packed array
+                                               struct_array_packing_error(node);
+                                       }
+                               }
+                               // range nodes are now redundant
+                               node->children.clear();
+                       }
+                       else if (node->children.size() == 1 && node->children[0]->type == AST_MULTIRANGE) {
+                               // packed 2D array, e.g. bit [3:0][63:0] a
+                               auto rnode = node->children[0];
+                               if (rnode->children.size() != 2) {
+                                       // packed arrays can only be 2D
+                                       struct_array_packing_error(node);
+                               }
+                               int array_count = range_width(node, rnode->children[0]);
+                               width = range_width(node, rnode->children[1]);
+                               save_struct_array_width(node, width);
+                               width *= array_count;
+                               // range nodes are now redundant
+                               node->children.clear();
+                       }
+                       else if (node->range_left < 0) {
+                               // 1 bit signal: bit, logic or reg
+                               width = 1;
+                       }
+                       else {
+                               // already resolved and compacted
+                               width = node->range_left - node->range_right + 1;
+                       }
+                       if (is_union) {
+                               node->range_right = base_offset;
+                               node->range_left = base_offset + width - 1;
+                       }
+                       else {
+                               node->range_right = base_offset + offset;
+                               node->range_left = base_offset + offset + width - 1;
+                       }
+                       node->range_valid = true;
+               }
+               if (is_union) {
+                       // check that all members have the same size
+                       if (packed_width == -1) {
+                               // first member
+                               packed_width = width;
+                       }
+                       else {
+                               if (packed_width != width) {
+
+                                       log_file_error(node->filename, node->location.first_line, "member %s of a packed union has %d bits, expecting %d\n", node->str.c_str(), width, packed_width);
+                               }
+                       }
+               }
+               else {
+                       offset += width;
+               }
+       }
+       return (is_union ? packed_width : offset);
+}
+
+[[noreturn]] static void struct_op_error(AstNode *node)
+{
+       log_file_error(node->filename, node->location.first_line, "Unsupported operation for struct/union member %s\n", node->str.c_str()+1);
+}
+
+static AstNode *node_int(int ival)
+{
+       return AstNode::mkconst_int(ival, true);
+}
+
+static AstNode *multiply_by_const(AstNode *expr_node, int stride)
+{
+       return new AstNode(AST_MUL, expr_node, node_int(stride));
+}
+
+static AstNode *offset_indexed_range(int offset, int stride, AstNode *left_expr, AstNode *right_expr)
+{
+       // adjust the range expressions to add an offset into the struct
+       // and maybe index using an array stride
+       auto left  = left_expr->clone();
+       auto right = right_expr->clone();
+       if (stride > 1) {
+               // newleft = (left + 1) * stride - 1
+               left  = new AstNode(AST_SUB, multiply_by_const(new AstNode(AST_ADD, left, node_int(1)), stride), node_int(1));
+               // newright = right * stride
+               right = multiply_by_const(right, stride);
+       }
+       // add the offset
+       if (offset) {
+               left  = new AstNode(AST_ADD, node_int(offset), left);
+               right = new AstNode(AST_ADD, node_int(offset), right);
+       }
+       return new AstNode(AST_RANGE, left, right);
+}
+
+static AstNode *make_struct_index_range(AstNode *node, AstNode *rnode, int stride, int offset)
+{
+       // generate a range node to perform either bit or array indexing
+       if (rnode->children.size() == 1) {
+               // index e.g. s.a[i]
+               return offset_indexed_range(offset, stride, rnode->children[0], rnode->children[0]);
+       }
+       else if (rnode->children.size() == 2) {
+               // slice e.g. s.a[i:j]
+               return offset_indexed_range(offset, stride, rnode->children[0], rnode->children[1]);
+       }
+       else {
+               struct_op_error(node);
+       }
+}
+
+static AstNode *slice_range(AstNode *rnode, AstNode *snode)
+{
+       // apply the bit slice indicated by snode to the range rnode
+       log_assert(rnode->type==AST_RANGE);
+       auto left  = rnode->children[0];
+       auto right = rnode->children[1];
+       log_assert(snode->type==AST_RANGE);
+       auto slice_left  = snode->children[0];
+       auto slice_right = snode->children[1];
+       auto width = new AstNode(AST_SUB, slice_left->clone(), slice_right->clone());
+       right = new AstNode(AST_ADD, right->clone(), slice_right->clone());
+       left  = new AstNode(AST_ADD, right->clone(), width);
+       return new AstNode(AST_RANGE, left, right);
+}
+
+
+static AstNode *make_struct_member_range(AstNode *node, AstNode *member_node)
+{
+       // 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
+       // such as array indexing or slicing
+       int range_left = member_node->range_left;
+       int range_right = member_node->range_right;
+       if (node->children.empty()) {
+               // no range operations apply, return the whole width
+               return make_range(range_left, range_right);
+       }
+       int stride = get_struct_array_width(member_node);
+       if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
+               // bit or array indexing e.g. s.a[2] or s.a[1:0]
+               return make_struct_index_range(node, node->children[0], stride, range_right);
+       }
+       else if (node->children.size() == 1 && node->children[0]->type == AST_MULTIRANGE) {
+               // multirange, i.e. bit slice after array index, e.g. s.a[i][p:q]
+               log_assert(stride > 1);
+               auto mrnode = node->children[0];
+               auto element_range = make_struct_index_range(node, mrnode->children[0], stride, range_right);
+               // then apply bit slice range
+               auto range = slice_range(element_range, mrnode->children[1]);
+               delete element_range;
+               return range;
+       }
+       else {
+               struct_op_error(node);
+       }
+}
+
+static void add_members_to_scope(AstNode *snode, std::string name)
+{
+       // add all the members in a struct or union to local scope
+       // in case later referenced in assignments
+       log_assert(snode->type==AST_STRUCT || snode->type==AST_UNION);
+       for (auto *node : snode->children) {
+               if (node->type != AST_STRUCT_ITEM) {
+                       // embedded struct or union
+                       add_members_to_scope(node, name + "." + node->str);
+               }
+               else {
+                       auto member_name = name + "." + node->str;
+                       current_scope[member_name] = node;
+               }
+       }
+}
+
+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)
+{
+       // create a wire for the packed struct
+       auto wnode = new AstNode(AST_WIRE);
+       wnode->str = name;
+       wnode->is_logic = true;
+       wnode->range_valid = true;
+       wnode->is_signed = template_node->is_signed;
+       int offset = get_max_offset(template_node);
+       auto range = make_range(offset, 0);
+       wnode->children.push_back(range);
+       // make sure this node is the one in scope for this name
+       current_scope[name] = wnode;
+       // add all the struct members to scope under the wire's name
+       add_members_to_scope(template_node, name);
+       return wnode;
+}
+
+// check if a node or its children contains an assignment to the given variable
+static bool node_contains_assignment_to(const AstNode* node, const AstNode* var)
+{
+       if (node->type == AST_ASSIGN_EQ || node->type == AST_ASSIGN_LE) {
+               // current node is iteslf an assignment
+               log_assert(node->children.size() >= 2);
+               const AstNode* lhs = node->children[0];
+               if (lhs->type == AST_IDENTIFIER && lhs->str == var->str)
+                       return false;
+       }
+       for (const AstNode* child : node->children) {
+               // if this child shadows the given variable
+               if (child != var && child->str == var->str && child->type == AST_WIRE)
+                       break; // skip the remainder of this block/scope
+               // depth-first short circuit
+               if (!node_contains_assignment_to(child, var))
+                       return false;
+       }
+       return true;
+}
+
+static std::string prefix_id(const std::string &prefix, const std::string &str)
+{
+       log_assert(!prefix.empty() && (prefix.front() == '$' || prefix.front() == '\\'));
+       log_assert(!str.empty() && (str.front() == '$' || str.front() == '\\'));
+       log_assert(prefix.back() == '.');
+       if (str.front() == '\\')
+               return prefix + str.substr(1);
+       return prefix + str;
+}
+
 // convert the AST into a simpler AST that has all parameters substituted by their
 // values, unrolled for-loops, expanded generate blocks, etc. when this function
 // is done with an AST it can be converted into RTLIL using genRTLIL().
@@ -367,6 +758,9 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        // also merge multiple declarations for the same wire (e.g. "output foobar; reg foobar;")
        if (type == AST_MODULE) {
                current_scope.clear();
+               std::set<std::string> existing;
+               int counter = 0;
+               label_genblks(existing, counter);
                std::map<std::string, AstNode*> this_wire_scope;
                for (size_t i = 0; i < children.size(); i++) {
                        AstNode *node = children[i];
@@ -463,7 +857,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                while (node->simplify(true, false, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM))
                                        did_something = true;
                        if (node->type == AST_ENUM) {
-                               for (auto enode YS_ATTRIBUTE(unused) : node->children){
+                               for (auto enode : node->children){
                                        log_assert(enode->type==AST_ENUM_ITEM);
                                        while (node->simplify(true, false, false, 1, -1, false, in_param))
                                                did_something = true;
@@ -567,6 +961,32 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                }
                break;
 
+       case AST_STRUCT:
+       case AST_UNION:
+               if (!basic_prep) {
+                       for (auto *node : children) {
+                               // resolve any ranges
+                               while (!node->basic_prep && node->simplify(true, false, false, stage, -1, false, false)) {
+                                       did_something = true;
+                               }
+                       }
+                       // determine member offsets and widths
+                       size_packed_struct(this, 0);
+
+                       // instance rather than just a type in a typedef or outer struct?
+                       if (!str.empty() && str[0] == '\\') {
+                               // instance so add a wire for the packed structure
+                               auto wnode = make_packed_struct(this, str);
+                               log_assert(current_ast_mod);
+                               current_ast_mod->children.push_back(wnode);
+                       }
+                       basic_prep = true;
+               }
+               break;
+
+       case AST_STRUCT_ITEM:
+               break;
+
        case AST_ENUM:
                //log("\nENUM %s: %d child %d\n", str.c_str(), basic_prep, children[0]->basic_prep);
                if (!basic_prep) {
@@ -608,6 +1028,8 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        case AST_TO_BITS:
        case AST_TO_SIGNED:
        case AST_TO_UNSIGNED:
+       case AST_SELFSZ:
+       case AST_CAST_SIZE:
        case AST_CONCAT:
        case AST_REPLICATE:
        case AST_REDUCE_AND:
@@ -784,6 +1206,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        bool in_param_here = in_param;
                        if (i == 0 && (type == AST_REPLICATE || type == AST_WIRE))
                                const_fold_here = true, in_param_here = true;
+                       if (i == 0 && (type == AST_GENIF || type == AST_GENCASE))
+                               in_param_here = true;
+                       if (i == 1 && (type == AST_FOR || type == AST_GENFOR))
+                               in_param_here = true;
                        if (type == AST_PARAMETER || type == AST_LOCALPARAM)
                                const_fold_here = true;
                        if (i == 0 && (type == AST_ASSIGN || type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE))
@@ -792,6 +1218,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                current_block = this;
                                current_block_child = children[i];
                        }
+                       if (!in_param_here && type == AST_FCALL) {
+                               bool recommend_const_eval = false;
+                               bool require_const_eval = has_const_only_constructs(recommend_const_eval);
+                               in_param_here = recommend_const_eval || require_const_eval;
+                       }
                        if ((type == AST_ALWAYS || type == AST_INITIAL) && children[i]->type == AST_BLOCK)
                                current_top_block = children[i];
                        if (i == 0 && child_0_is_self_determined)
@@ -883,10 +1314,12 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        // resolve typedefs
        if (type == AST_TYPEDEF) {
                log_assert(children.size() == 1);
-               log_assert(children[0]->type == AST_WIRE || children[0]->type == AST_MEMORY);
-               while(children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param))
+               auto type_node = children[0];
+               log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION);
+               while (type_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {
                        did_something = true;
-               log_assert(!children[0]->is_custom_type);
+               }
+               log_assert(!type_node->is_custom_type);
        }
 
        // resolve types of wires
@@ -894,100 +1327,60 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                if (is_custom_type) {
                        log_assert(children.size() >= 1);
                        log_assert(children[0]->type == AST_WIRETYPE);
-                       if (!current_scope.count(children[0]->str))
-                               log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", children[0]->str.c_str());
-                       AstNode *resolved_type = current_scope.at(children[0]->str);
-                       if (resolved_type->type != AST_TYPEDEF)
-                               log_file_error(filename, location.first_line, "`%s' does not name a type\n", children[0]->str.c_str());
-                       log_assert(resolved_type->children.size() == 1);
-                       AstNode *templ = resolved_type->children[0];
+                       auto type_name = children[0]->str;
+                       if (!current_scope.count(type_name)) {
+                               log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str());
+                       }
+                       AstNode *resolved_type_node = current_scope.at(type_name);
+                       if (resolved_type_node->type != AST_TYPEDEF)
+                               log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str());
+                       log_assert(resolved_type_node->children.size() == 1);
+                       AstNode *template_node = resolved_type_node->children[0];
+
+                       // Ensure typedef itself is fully simplified
+                       while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
+
+                       if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) {
+                               // replace with wire representing the packed structure
+                               newNode = make_packed_struct(template_node, str);
+                               // add original input/output attribute to resolved wire
+                               newNode->is_input = this->is_input;
+                               newNode->is_output = this->is_output;
+                               current_scope[str] = this;
+                               goto apply_newNode;
+                       }
+
                        // Remove type reference
                        delete children[0];
                        children.erase(children.begin());
 
-                       // Ensure typedef itself is fully simplified
-                       while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
-
                        if (type == AST_WIRE)
-                               type = templ->type;
-                       is_reg = templ->is_reg;
-                       is_logic = templ->is_logic;
-                       is_signed = templ->is_signed;
-                       is_string = templ->is_string;
-                       is_custom_type = templ->is_custom_type;
-
-                       range_valid = templ->range_valid;
-                       range_swapped = templ->range_swapped;
-                       range_left = templ->range_left;
-                       range_right = templ->range_right;
-                       attributes["\\wiretype"] = mkconst_str(resolved_type->str);
-                       //check if enum
-                       if (templ->attributes.count("\\enum_type")){
-                               //get reference to enum node:
-                               std::string enum_type = templ->attributes["\\enum_type"]->str.c_str();
-                               //                              log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type));
-                               //                              log("current scope:\n");
-                               //                              for (auto &it : current_scope)
-                               //                                      log("  %s\n", it.first.c_str());
-                               log_assert(current_scope.count(enum_type) == 1);
-                               AstNode *enum_node = current_scope.at(enum_type);
-                               log_assert(enum_node->type == AST_ENUM);
-                               //get width from 1st enum item:
-                               log_assert(enum_node->children.size() >= 1);
-                               AstNode *enum_item0 = enum_node->children[0];
-                               log_assert(enum_item0->type == AST_ENUM_ITEM);
-                               int width;
-                               if (!enum_item0->range_valid)
-                                       width = 1;
-                               else if (enum_item0->range_swapped)
-                                       width = enum_item0->range_right - enum_item0->range_left + 1;
-                               else
-                                       width = enum_item0->range_left - enum_item0->range_right + 1;
-                               log_assert(width > 0);
-                               //add declared enum items:
-                               for (auto enum_item : enum_node->children){
-                                       log_assert(enum_item->type == AST_ENUM_ITEM);
-                                       //get is_signed
-                                       bool is_signed;
-                                       if (enum_item->children.size() == 1){
-                                               is_signed = false;
-                                       } else if (enum_item->children.size() == 2){
-                                               log_assert(enum_item->children[1]->type == AST_RANGE);
-                                               is_signed = enum_item->children[1]->is_signed;
-                                       } else {
-                                               log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n",
-                                                                 enum_item->children.size(),
-                                                                 enum_item->str.c_str(), enum_node->str.c_str()
-                                               );
-                                       }
-                                       //start building attribute string
-                                       std::string enum_item_str = "\\enum_value_";
-                                       //get enum item value
-                                       if(enum_item->children[0]->type != AST_CONSTANT){
-                                               log_error("expected const, got %s for %s (%s)\n",
-                                                                 type2str(enum_item->children[0]->type).c_str(),
-                                                                 enum_item->str.c_str(), enum_node->str.c_str()
-                                                               );
-                                       }
-                                       RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed);
-                                       enum_item_str.append(val.as_string());
-                                       //set attribute for available val to enum item name mappings
-                                       attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str);
-                               }
-                       }
+                               type = template_node->type;
+                       is_reg = template_node->is_reg;
+                       is_logic = template_node->is_logic;
+                       is_signed = template_node->is_signed;
+                       is_string = template_node->is_string;
+                       is_custom_type = template_node->is_custom_type;
+
+                       range_valid = template_node->range_valid;
+                       range_swapped = template_node->range_swapped;
+                       range_left = template_node->range_left;
+                       range_right = template_node->range_right;
+
+                       attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
+
+                       // if an enum then add attributes to support simulator tracing
+                       annotateTypedEnums(template_node);
 
                        // Insert clones children from template at beginning
-                       for (int i  = 0; i < GetSize(templ->children); i++)
-                               children.insert(children.begin() + i, templ->children[i]->clone());
+                       for (int i  = 0; i < GetSize(template_node->children); i++)
+                               children.insert(children.begin() + i, template_node->children[i]->clone());
 
                        if (type == AST_MEMORY && GetSize(children) == 1) {
                                // Single-bit memories must have [0:0] range
-                               AstNode *rng = new AstNode(AST_RANGE);
-                               rng->children.push_back(AstNode::mkconst_int(0, true));
-                               rng->children.push_back(AstNode::mkconst_int(0, true));
+                               AstNode *rng = make_range(0, 0);
                                children.insert(children.begin(), rng);
                        }
-
                        did_something = true;
                }
                log_assert(!is_custom_type);
@@ -1000,29 +1393,29 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        log_assert(children[1]->type == AST_WIRETYPE);
                        if (!current_scope.count(children[1]->str))
                                log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", children[1]->str.c_str());
-                       AstNode *resolved_type = current_scope.at(children[1]->str);
-                       if (resolved_type->type != AST_TYPEDEF)
+                       AstNode *resolved_type_node = current_scope.at(children[1]->str);
+                       if (resolved_type_node->type != AST_TYPEDEF)
                                log_file_error(filename, location.first_line, "`%s' does not name a type\n", children[1]->str.c_str());
-                       log_assert(resolved_type->children.size() == 1);
-                       AstNode *templ = resolved_type->children[0];
+                       log_assert(resolved_type_node->children.size() == 1);
+                       AstNode *template_node = resolved_type_node->children[0];
                        delete children[1];
                        children.pop_back();
 
                        // Ensure typedef itself is fully simplified
-                       while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
+                       while(template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
 
-                       if (templ->type == AST_MEMORY)
+                       if (template_node->type == AST_MEMORY)
                                log_file_error(filename, location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str());
-                       is_signed = templ->is_signed;
-                       is_string = templ->is_string;
-                       is_custom_type = templ->is_custom_type;
-
-                       range_valid = templ->range_valid;
-                       range_swapped = templ->range_swapped;
-                       range_left = templ->range_left;
-                       range_right = templ->range_right;
-                       attributes["\\wiretype"] = mkconst_str(resolved_type->str);
-                       for (auto template_child : templ->children)
+                       is_signed = template_node->is_signed;
+                       is_string = template_node->is_string;
+                       is_custom_type = template_node->is_custom_type;
+
+                       range_valid = template_node->range_valid;
+                       range_swapped = template_node->range_swapped;
+                       range_left = template_node->range_left;
+                       range_right = template_node->range_right;
+                       attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
+                       for (auto template_child : template_node->children)
                                children.push_back(template_child->clone());
                        did_something = true;
                }
@@ -1079,7 +1472,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                }
                if (old_range_valid != range_valid)
                        did_something = true;
-               if (range_valid && range_left >= 0 && range_right > range_left) {
+               if (range_valid && range_right > range_left) {
                        int tmp = range_right;
                        range_right = range_left;
                        range_left = tmp;
@@ -1097,6 +1490,25 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                range_swapped = children[0]->range_swapped;
                                range_left = children[0]->range_left;
                                range_right = children[0]->range_right;
+                               bool force_upto = false, force_downto = false;
+                               if (attributes.count(ID::force_upto)) {
+                                       AstNode *val = attributes[ID::force_upto];
+                                       if (val->type != AST_CONSTANT)
+                                               log_file_error(filename, location.first_line, "Attribute `force_upto' with non-constant value!\n");
+                                       force_upto = val->asAttrConst().as_bool();
+                               }
+                               if (attributes.count(ID::force_downto)) {
+                                       AstNode *val = attributes[ID::force_downto];
+                                       if (val->type != AST_CONSTANT)
+                                               log_file_error(filename, location.first_line, "Attribute `force_downto' with non-constant value!\n");
+                                       force_downto = val->asAttrConst().as_bool();
+                               }
+                               if (force_upto && force_downto)
+                                       log_file_error(filename, location.first_line, "Attributes `force_downto' and `force_upto' cannot be both set!\n");
+                               if ((force_upto && !range_swapped) || (force_downto && range_swapped)) {
+                                       std::swap(range_left, range_right);
+                                       range_swapped = force_upto;
+                               }
                        }
                } else {
                        if (!range_valid)
@@ -1113,11 +1525,13 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        {
                int total_size = 1;
                multirange_dimensions.clear();
+               multirange_swapped.clear();
                for (auto range : children[1]->children) {
                        if (!range->range_valid)
                                log_file_error(filename, location.first_line, "Non-constant range on memory decl.\n");
                        multirange_dimensions.push_back(min(range->range_left, range->range_right));
                        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);
                        total_size *= multirange_dimensions.back();
                }
                delete children[1];
@@ -1130,9 +1544,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        {
                AstNode *index_expr = nullptr;
 
+               integer = children[0]->children.size(); // save original number of dimensions for $size() etc.
                for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++)
                {
-                       if (GetSize(children[0]->children) < i)
+                       if (GetSize(children[0]->children) <= i)
                                log_file_error(filename, location.first_line, "Insufficient number of array indices for %s.\n", log_id(str));
 
                        AstNode *new_index_expr = children[0]->children[i]->children.at(0)->clone();
@@ -1196,10 +1611,34 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                }
        }
 
+       if (type == AST_IDENTIFIER && !basic_prep) {
+               // check if a plausible struct member sss.mmmm
+               std::string sname;
+               if (name_has_dot(str, sname)) {
+                       if (current_scope.count(str) > 0) {
+                               auto item_node = current_scope[str];
+                               if (item_node->type == AST_STRUCT_ITEM) {
+                                       // structure member, rewrite this node to reference the packed struct wire
+                                       auto range = make_struct_member_range(this, item_node);
+                                       newNode = new AstNode(AST_IDENTIFIER, range);
+                                       newNode->str = sname;
+                                       newNode->basic_prep = true;
+                                       goto apply_newNode;
+                               }
+                       }
+               }
+       }
        // annotate identifiers using scope resolution and create auto-wires as needed
        if (type == AST_IDENTIFIER) {
                if (current_scope.count(str) == 0) {
                        AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod;
+                       const std::string& mod_scope = current_scope_ast->str;
+                       if (str[0] == '\\' && str.substr(0, mod_scope.size()) == mod_scope) {
+                               std::string new_str = "\\" + str.substr(mod_scope.size() + 1);
+                               if (current_scope.count(new_str)) {
+                                       str = new_str;
+                               }
+                       }
                        for (auto node : current_scope_ast->children) {
                                //log("looking at mod scope child %s\n", type2str(node->type).c_str());
                                switch (node->type) {
@@ -1304,6 +1743,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 
                newNode = new AstNode(AST_IDENTIFIER, children[1]->clone());
                newNode->str = wire_id;
+               newNode->integer = integer; // save original number of dimensions for $size() etc.
                newNode->id2ast = wire;
                goto apply_newNode;
        }
@@ -1345,25 +1785,27 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                body_ast->children.size() == 1 && body_ast->children.at(0)->type == AST_GENBLOCK)
                        body_ast = body_ast->children.at(0);
 
+               const char* loop_type_str = "procedural";
+               const char* var_type_str = "register";
+               AstNodeType var_type = AST_WIRE;
+               if (type == AST_GENFOR) {
+                       loop_type_str = "generate";
+                       var_type_str = "genvar";
+                       var_type = AST_GENVAR;
+               }
+
                if (init_ast->type != AST_ASSIGN_EQ)
-                       log_file_error(filename, location.first_line, "Unsupported 1st expression of generate for-loop!\n");
+                       log_file_error(filename, location.first_line, "Unsupported 1st expression of %s for-loop!\n", loop_type_str);
                if (next_ast->type != AST_ASSIGN_EQ)
-                       log_file_error(filename, location.first_line, "Unsupported 3rd expression of generate for-loop!\n");
+                       log_file_error(filename, location.first_line, "Unsupported 3rd expression of %s for-loop!\n", loop_type_str);
 
-               if (type == AST_GENFOR) {
-                       if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != AST_GENVAR)
-                               log_file_error(filename, location.first_line, "Left hand side of 1st expression of generate for-loop is not a gen var!\n");
-                       if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != AST_GENVAR)
-                               log_file_error(filename, location.first_line, "Left hand side of 3rd expression of generate for-loop is not a gen var!\n");
-               } else {
-                       if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != AST_WIRE)
-                               log_file_error(filename, location.first_line, "Left hand side of 1st expression of generate for-loop is not a register!\n");
-                       if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != AST_WIRE)
-                               log_file_error(filename, location.first_line, "Left hand side of 3rd expression of generate for-loop is not a register!\n");
-               }
+               if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != var_type)
+                       log_file_error(filename, location.first_line, "Left hand side of 1st expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str);
+               if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != var_type)
+                       log_file_error(filename, location.first_line, "Left hand side of 3rd expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str);
 
                if (init_ast->children[0]->id2ast != next_ast->children[0]->id2ast)
-                       log_file_error(filename, location.first_line, "Incompatible left-hand sides in 1st and 3rd expression of generate for-loop!\n");
+                       log_file_error(filename, location.first_line, "Incompatible left-hand sides in 1st and 3rd expression of %s for-loop!\n", loop_type_str);
 
                // eval 1st expression
                AstNode *varbuf = init_ast->children[1]->clone();
@@ -1375,7 +1817,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                }
 
                if (varbuf->type != AST_CONSTANT)
-                       log_file_error(filename, location.first_line, "Right hand side of 1st expression of generate for-loop is not constant!\n");
+                       log_file_error(filename, location.first_line, "Right hand side of 1st expression of %s for-loop is not constant!\n", loop_type_str);
 
                auto resolved = current_scope.at(init_ast->children[0]->str);
                if (resolved->range_valid) {
@@ -1416,7 +1858,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        }
 
                        if (buf->type != AST_CONSTANT)
-                               log_file_error(filename, location.first_line, "2nd expression of generate for-loop is not constant!\n");
+                               log_file_error(filename, location.first_line, "2nd expression of %s for-loop is not constant!\n", loop_type_str);
 
                        if (buf->integer == 0) {
                                delete buf;
@@ -1426,23 +1868,28 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 
                        // expand body
                        int index = varbuf->children[0]->integer;
-                       if (body_ast->type == AST_GENBLOCK)
-                               buf = body_ast->clone();
-                       else
-                               buf = new AstNode(AST_GENBLOCK, body_ast->clone());
-                       if (buf->str.empty()) {
-                               std::stringstream sstr;
-                               sstr << "$genblock$" << filename << ":" << location.first_line << "$" << (autoidx++);
-                               buf->str = sstr.str();
-                       }
-                       std::map<std::string, std::string> name_map;
+                       log_assert(body_ast->type == AST_GENBLOCK || body_ast->type == AST_BLOCK);
+                       log_assert(!body_ast->str.empty());
+                       buf = body_ast->clone();
+
                        std::stringstream sstr;
                        sstr << buf->str << "[" << index << "].";
-                       buf->expand_genblock(varbuf->str, sstr.str(), name_map);
+                       std::string prefix = sstr.str();
+
+                       // create a scoped localparam for the current value of the loop variable
+                       AstNode *local_index = varbuf->clone();
+                       size_t pos = local_index->str.rfind('.');
+                       if (pos != std::string::npos) // remove outer prefix
+                               local_index->str = "\\" + local_index->str.substr(pos + 1);
+                       local_index->str = prefix_id(prefix, local_index->str);
+                       current_scope[local_index->str] = local_index;
+                       current_ast_mod->children.push_back(local_index);
+
+                       buf->expand_genblock(prefix);
 
                        if (type == AST_GENFOR) {
                                for (size_t i = 0; i < buf->children.size(); i++) {
-                                       buf->children[i]->simplify(false, false, false, stage, -1, false, false);
+                                       buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
                                        current_ast_mod->children.push_back(buf->children[i]);
                                }
                        } else {
@@ -1462,7 +1909,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        }
 
                        if (buf->type != AST_CONSTANT)
-                               log_file_error(filename, location.first_line, "Right hand side of 3rd expression of generate for-loop is not constant (%s)!\n", type2str(buf->type).c_str());
+                               log_file_error(filename, location.first_line, "Right hand side of 3rd expression of %s for-loop is not constant (%s)!\n", loop_type_str, type2str(buf->type).c_str());
 
                        delete varbuf->children[0];
                        varbuf->children[0] = buf;
@@ -1486,14 +1933,16 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        {
                for (size_t i = 0; i < children.size(); i++)
                        if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF)
-                               log_file_error(children[i]->filename, children[i]->location.first_line, "Local declaration in unnamed block is an unsupported SystemVerilog feature!\n");
+                       {
+                               log_assert(!VERILOG_FRONTEND::sv_mode);
+                               log_file_error(children[i]->filename, children[i]->location.first_line, "Local declaration in unnamed block is only supported in SystemVerilog mode!\n");
+                       }
        }
 
        // transform block with name
        if (type == AST_BLOCK && !str.empty())
        {
-               std::map<std::string, std::string> name_map;
-               expand_genblock(std::string(), str + ".", name_map);
+               expand_genblock(str + ".");
 
                std::vector<AstNode*> new_children;
                for (size_t i = 0; i < children.size(); i++)
@@ -1513,12 +1962,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        if (type == AST_GENBLOCK && children.size() != 0)
        {
                if (!str.empty()) {
-                       std::map<std::string, std::string> name_map;
-                       expand_genblock(std::string(), str + ".", name_map);
+                       expand_genblock(str + ".");
                }
 
                for (size_t i = 0; i < children.size(); i++) {
-                       children[i]->simplify(false, false, false, stage, -1, false, false);
+                       children[i]->simplify(const_fold, false, false, stage, -1, false, false);
                        current_ast_mod->children.push_back(children[i]);
                }
 
@@ -1550,12 +1998,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                buf = new AstNode(AST_GENBLOCK, buf);
 
                        if (!buf->str.empty()) {
-                               std::map<std::string, std::string> name_map;
-                               buf->expand_genblock(std::string(), buf->str + ".", name_map);
+                               buf->expand_genblock(buf->str + ".");
                        }
 
                        for (size_t i = 0; i < buf->children.size(); i++) {
-                               buf->children[i]->simplify(false, false, false, stage, -1, false, false);
+                               buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
                                current_ast_mod->children.push_back(buf->children[i]);
                        }
 
@@ -1605,7 +2052,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                        continue;
 
                                buf = child->clone();
-                               while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+                               while (buf->simplify(true, false, false, stage, width_hint, sign_hint, true)) { }
                                if (buf->type != AST_CONSTANT) {
                                        // for (auto f : log_files)
                                        //      dumpAst(f, "verilog-ast> ");
@@ -1629,12 +2076,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        buf = selected_case->clone();
 
                        if (!buf->str.empty()) {
-                               std::map<std::string, std::string> name_map;
-                               buf->expand_genblock(std::string(), buf->str + ".", name_map);
+                               buf->expand_genblock(buf->str + ".");
                        }
 
                        for (size_t i = 0; i < buf->children.size(); i++) {
-                               buf->children[i]->simplify(false, false, false, stage, -1, false, false);
+                               buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
                                current_ast_mod->children.push_back(buf->children[i]);
                        }
 
@@ -1739,8 +2185,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 
                        AstNode *node = children_list[1];
                        if (op_type != AST_POS)
-                               for (size_t i = 2; i < children_list.size(); i++)
+                               for (size_t i = 2; i < children_list.size(); i++) {
                                        node = new AstNode(op_type, node, children_list[i]);
+                                       node->location = location;
+                               }
                        if (invert_results)
                                node = new AstNode(AST_BIT_NOT, node);
 
@@ -1754,8 +2202,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        }
 
        // replace dynamic ranges in left-hand side expressions (e.g. "foo[bar] <= 1'b1;") with
-       // a big case block that selects the correct single-bit assignment.
-       if (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) {
+       // either a big case block that selects the correct single-bit assignment, or mask and
+       // shift operations.
+       if (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE)
+       {
                if (children[0]->type != AST_IDENTIFIER || children[0]->children.size() == 0)
                        goto skip_dynamic_range_lvalue_expansion;
                if (children[0]->children[0]->range_valid || did_something)
@@ -1764,10 +2214,13 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        goto skip_dynamic_range_lvalue_expansion;
                if (!children[0]->id2ast->range_valid)
                        goto skip_dynamic_range_lvalue_expansion;
+
                int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
                int result_width = 1;
+
                AstNode *shift_expr = NULL;
                AstNode *range = children[0]->children[0];
+
                if (range->children.size() == 1) {
                        shift_expr = range->children[0]->clone();
                } else {
@@ -1780,19 +2233,115 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                log_file_error(filename, location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str());
                        result_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1;
                }
-               did_something = true;
-               newNode = new AstNode(AST_CASE, shift_expr);
-               for (int i = 0; i < source_width; i++) {
-                       int start_bit = children[0]->id2ast->range_right + i;
-                       AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true));
+
+               bool use_case_method = false;
+
+               if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) {
+                       AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk);
+                       while (node->simplify(true, false, false, stage, -1, false, false)) { }
+                       if (node->type != AST_CONSTANT)
+                               log_file_error(filename, location.first_line, "Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str());
+                       if (node->asAttrConst().as_bool())
+                               use_case_method = true;
+               }
+
+               if (!use_case_method && current_always->detect_latch(children[0]->str))
+                       use_case_method = true;
+
+               if (use_case_method)
+               {
+                       // big case block
+
+                       did_something = true;
+                       newNode = new AstNode(AST_CASE, shift_expr);
+                       for (int i = 0; i < source_width; i++) {
+                               int start_bit = children[0]->id2ast->range_right + i;
+                               int end_bit = std::min(start_bit+result_width,source_width) - 1;
+                               AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true));
+                               AstNode *lvalue = children[0]->clone();
+                               lvalue->delete_children();
+                               lvalue->children.push_back(new AstNode(AST_RANGE,
+                                               mkconst_int(end_bit, true), mkconst_int(start_bit, true)));
+                               cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone())));
+                               newNode->children.push_back(cond);
+                       }
+               }
+               else
+               {
+                       // mask and shift operations, disabled for now
+
+                       AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
+                       wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", filename.c_str(), location.first_line, autoidx++);
+                       wire_mask->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
+                       wire_mask->is_logic = true;
+                       while (wire_mask->simplify(true, false, false, 1, -1, false, false)) { }
+                       current_ast_mod->children.push_back(wire_mask);
+
+                       AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
+                       wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", filename.c_str(), location.first_line, autoidx++);
+                       wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
+                       wire_data->is_logic = true;
+                       while (wire_data->simplify(true, false, false, 1, -1, false, false)) { }
+                       current_ast_mod->children.push_back(wire_data);
+
+                       did_something = true;
+                       newNode = new AstNode(AST_BLOCK);
+
                        AstNode *lvalue = children[0]->clone();
                        lvalue->delete_children();
-                       int end_bit = std::min(start_bit+result_width,source_width) - 1;
-                       lvalue->children.push_back(new AstNode(AST_RANGE,
-                                       mkconst_int(end_bit, true), mkconst_int(start_bit, true)));
-                       cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone())));
-                       newNode->children.push_back(cond);
+
+                       AstNode *ref_mask = new AstNode(AST_IDENTIFIER);
+                       ref_mask->str = wire_mask->str;
+                       ref_mask->id2ast = wire_mask;
+                       ref_mask->was_checked = true;
+
+                       AstNode *ref_data = new AstNode(AST_IDENTIFIER);
+                       ref_data->str = wire_data->str;
+                       ref_data->id2ast = wire_data;
+                       ref_data->was_checked = true;
+
+                       AstNode *old_data = lvalue->clone();
+                       if (type == AST_ASSIGN_LE)
+                               old_data->lookahead = true;
+
+                       AstNode *shamt = shift_expr;
+
+                       int shamt_width_hint = 0;
+                       bool shamt_sign_hint = true;
+                       shamt->detectSignWidth(shamt_width_hint, shamt_sign_hint);
+
+                       int start_bit = children[0]->id2ast->range_right;
+                       bool use_shift = shamt_sign_hint;
+
+                       if (start_bit != 0) {
+                               shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
+                               use_shift = true;
+                       }
+
+                       AstNode *t;
+
+                       t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
+                       if (use_shift)
+                               t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt->clone()));
+                       else
+                               t = new AstNode(AST_SHIFT_LEFT, t, shamt->clone());
+                       t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t);
+                       newNode->children.push_back(t);
+
+                       t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone());
+                       if (use_shift)
+                               t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt));
+                       else
+                               t = new AstNode(AST_SHIFT_LEFT, t, shamt);
+                       t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t);
+                       newNode->children.push_back(t);
+
+                       t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask));
+                       t = new AstNode(AST_BIT_OR, t, ref_data);
+                       t = new AstNode(type, lvalue, t);
+                       newNode->children.push_back(t);
                }
+
                goto apply_newNode;
        }
 skip_dynamic_range_lvalue_expansion:;
@@ -2311,26 +2860,28 @@ skip_dynamic_range_lvalue_expansion:;
                                goto apply_newNode;
                        }
 
-                       if (str == "\\$size" || str == "\\$bits")
+                       if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right")
                        {
-                               if (str == "\\$bits" && children.size() != 1)
-                                       log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
-                                                       RTLIL::unescape_id(str).c_str(), int(children.size()));
-
-                               if (str == "\\$size" && children.size() != 1 && children.size() != 2)
-                                       log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
-                                                       RTLIL::unescape_id(str).c_str(), int(children.size()));
-
                                int dim = 1;
-                               if (str == "\\$size" && children.size() == 2) {
-                                       AstNode *buf = children[1]->clone();
-                                       // Evaluate constant expression
-                                       while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
-                                       dim = buf->asInt(false);
-                                       delete buf;
+                               if (str == "\\$bits") {
+                                       if (children.size() != 1)
+                                               log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
+                                                               RTLIL::unescape_id(str).c_str(), int(children.size()));
+                               } else {
+                                       if (children.size() != 1 && children.size() != 2)
+                                               log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
+                                                       RTLIL::unescape_id(str).c_str(), int(children.size()));
+                                       if (children.size() == 2) {
+                                               AstNode *buf = children[1]->clone();
+                                               // Evaluate constant expression
+                                               while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+                                               dim = buf->asInt(false);
+                                               delete buf;
+                                       }
                                }
                                AstNode *buf = children[0]->clone();
                                int mem_depth = 1;
+                               int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire
                                AstNode *id_ast = NULL;
 
                                // Is this needed?
@@ -2343,6 +2894,31 @@ skip_dynamic_range_lvalue_expansion:;
                                                id_ast = current_scope.at(buf->str);
                                        if (!id_ast)
                                                log_file_error(filename, location.first_line, "Failed to resolve identifier %s for width detection!\n", buf->str.c_str());
+                                       // 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 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
+                                       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) {
                                                // We got here only if the argument is a memory
                                                // Otherwise $size() and $bits() return the expression width
@@ -2355,29 +2931,58 @@ skip_dynamic_range_lvalue_expansion:;
                                                        } else
                                                                log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
                                                } else {
-                                                       // $size()
+                                                       // $size(), $left(), $right(), $high(), $low()
+                                                       int dims = 1;
                                                        if (mem_range->type == AST_RANGE) {
-                                                               if (!mem_range->range_valid)
-                                                                       log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str());
-                                                               int dims;
-                                                               if (id_ast->multirange_dimensions.empty())
-                                                                       dims = 1;
-                                                               else
+                                                               if (id_ast->multirange_dimensions.empty()) {
+                                                                       if (!mem_range->range_valid)
+                                                                               log_file_error(filename, location.first_line, "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 == 1)
-                                                                       width_hint = (dims > 1) ? id_ast->multirange_dimensions[1] : (mem_range->range_left - mem_range->range_right + 1);
-                                                               else if (dim <= dims) {
-                                                                       width_hint = id_ast->multirange_dimensions[2*dim-1];
-                                                               } else if ((dim > dims+1) || (dim < 0))
-                                                                       log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
-                                                       } else
+                                                                       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))
+                                                                               log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
+                                                               }
+                                                       } else {
                                                                log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
+                                                       }
                                                }
                                        }
+                                       width = high - low + 1;
+                               } else {
+                                       width = width_hint;
                                }
                                delete buf;
-
-                               newNode = mkconst_int(width_hint * mem_depth, false);
+                               if (str == "\\$high")
+                                       result = high;
+                               else if (str == "\\$low")
+                                       result = low;
+                               else if (str == "\\$left")
+                                       result = left;
+                               else if (str == "\\$right")
+                                       result = right;
+                               else if (str == "\\$size")
+                                       result = width;
+                               else {
+                                       result = width * mem_depth;
+                               }
+                               newNode = mkconst_int(result, false);
                                goto apply_newNode;
                        }
 
@@ -2571,27 +3176,33 @@ skip_dynamic_range_lvalue_expansion:;
                                log_file_error(filename, location.first_line, "Can't resolve task name `%s'.\n", str.c_str());
                }
 
-               AstNode *decl = current_scope[str];
 
                std::stringstream sstr;
-               sstr << "$func$" << str << "$" << filename << ":" << location.first_line << "$" << (autoidx++) << "$";
+               sstr << str << "$func$" << filename << ":" << location.first_line << "$" << (autoidx++) << '.';
                std::string prefix = sstr.str();
 
+               AstNode *decl = current_scope[str];
+               decl = decl->clone();
+               decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion
+               decl->expand_genblock(prefix);
+
                bool recommend_const_eval = false;
                bool require_const_eval = in_param ? false : has_const_only_constructs(recommend_const_eval);
-               if ((in_param || recommend_const_eval || require_const_eval) && !decl->attributes.count("\\via_celltype"))
+               if ((in_param || recommend_const_eval || require_const_eval) && !decl->attributes.count(ID::via_celltype))
                {
                        bool all_args_const = true;
                        for (auto child : children) {
                                while (child->simplify(true, false, false, 1, -1, false, true)) { }
-                               if (child->type != AST_CONSTANT)
+                               if (child->type != AST_CONSTANT && child->type != AST_REALVALUE)
                                        all_args_const = false;
                        }
 
                        if (all_args_const) {
-                               AstNode *func_workspace = current_scope[str]->clone();
+                               AstNode *func_workspace = decl->clone();
+                               func_workspace->str = prefix_id(prefix, "$result");
                                newNode = func_workspace->eval_const_function(this);
                                delete func_workspace;
+                               delete decl;
                                goto apply_newNode;
                        }
 
@@ -2602,8 +3213,6 @@ skip_dynamic_range_lvalue_expansion:;
                }
 
                size_t arg_count = 0;
-               std::map<std::string, std::string> replace_rules;
-               vector<AstNode*> added_mod_children;
                dict<std::string, AstNode*> wire_cache;
                vector<AstNode*> new_stmts;
                vector<AstNode*> output_assignments;
@@ -2613,16 +3222,17 @@ skip_dynamic_range_lvalue_expansion:;
                        log_assert(type == AST_FCALL);
 
                        AstNode *wire = NULL;
+                       std::string res_name = prefix_id(prefix, "$result");
                        for (auto child : decl->children)
-                               if (child->type == AST_WIRE && child->str == str)
+                               if (child->type == AST_WIRE && child->str == res_name)
                                        wire = child->clone();
                        log_assert(wire != NULL);
 
-                       wire->str = prefix + str;
                        wire->port_id = 0;
                        wire->is_input = false;
                        wire->is_output = false;
 
+                       current_scope[wire->str] = wire;
                        current_ast_mod->children.push_back(wire);
                        while (wire->simplify(true, false, false, 1, -1, false, false)) { }
 
@@ -2638,9 +3248,9 @@ skip_dynamic_range_lvalue_expansion:;
                        goto replace_fcall_with_id;
                }
 
-               if (decl->attributes.count("\\via_celltype"))
+               if (decl->attributes.count(ID::via_celltype))
                {
-                       std::string celltype = decl->attributes.at("\\via_celltype")->asAttrConst().decode_string();
+                       std::string celltype = decl->attributes.at(ID::via_celltype)->asAttrConst().decode_string();
                        std::string outport = str;
 
                        if (celltype.find(' ') != std::string::npos) {
@@ -2666,7 +3276,6 @@ skip_dynamic_range_lvalue_expansion:;
                                if (child->type == AST_WIRE && (child->is_input || child->is_output || (type == AST_FCALL && child->str == str)))
                                {
                                        AstNode *wire = child->clone();
-                                       wire->str = prefix + wire->str;
                                        wire->port_id = 0;
                                        wire->is_input = false;
                                        wire->is_output = false;
@@ -2709,14 +3318,15 @@ skip_dynamic_range_lvalue_expansion:;
                                if (wire_cache.count(child->str))
                                {
                                        wire = wire_cache.at(child->str);
-                                       if (wire->children.empty()) {
+                                       bool contains_value = wire->type == AST_LOCALPARAM;
+                                       if (wire->children.size() == contains_value) {
                                                for (auto c : child->children)
                                                        wire->children.push_back(c->clone());
                                        } else if (!child->children.empty()) {
                                                while (child->simplify(true, false, false, stage, -1, false, false)) { }
-                                               if (GetSize(child->children) == GetSize(wire->children)) {
+                                               if (GetSize(child->children) == GetSize(wire->children) - contains_value) {
                                                        for (int i = 0; i < GetSize(child->children); i++)
-                                                               if (*child->children.at(i) != *wire->children.at(i))
+                                                               if (*child->children.at(i) != *wire->children.at(i + contains_value))
                                                                        goto tcall_incompatible_wires;
                                                } else {
                                        tcall_incompatible_wires:
@@ -2727,30 +3337,51 @@ skip_dynamic_range_lvalue_expansion:;
                                else
                                {
                                        wire = child->clone();
-                                       wire->str = prefix + wire->str;
                                        wire->port_id = 0;
                                        wire->is_input = false;
                                        wire->is_output = false;
                                        wire->is_reg = true;
                                        wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
                                        if (child->type == AST_ENUM_ITEM)
-                                               wire->attributes["\\enum_base_type"] = child->attributes["\\enum_base_type"];
+                                               wire->attributes[ID::enum_base_type] = child->attributes[ID::enum_base_type];
 
                                        wire_cache[child->str] = wire;
 
+                                       current_scope[wire->str] = wire;
                                        current_ast_mod->children.push_back(wire);
-                                       added_mod_children.push_back(wire);
                                }
 
-                               if (child->type == AST_WIRE)
-                                       while (wire->simplify(true, false, false, 1, -1, false, false)) { }
-
-                               replace_rules[child->str] = wire->str;
-                               current_scope[wire->str] = wire;
+                               while (wire->simplify(true, false, false, 1, -1, false, false)) { }
 
                                if ((child->is_input || child->is_output) && arg_count < children.size())
                                {
                                        AstNode *arg = children[arg_count++]->clone();
+                                       // convert purely constant arguments into localparams
+                                       if (child->is_input && child->type == AST_WIRE && arg->type == AST_CONSTANT && node_contains_assignment_to(decl, child)) {
+                                               wire->type = AST_LOCALPARAM;
+                                               wire->attributes.erase(ID::nosync);
+                                               wire->children.insert(wire->children.begin(), arg->clone());
+                                               // args without a range implicitly have width 1
+                                               if (wire->children.back()->type != AST_RANGE) {
+                                                       // check if this wire is redeclared with an explicit size
+                                                       bool uses_explicit_size = false;
+                                                       for (const AstNode *other_child : decl->children)
+                                                               if (other_child->type == AST_WIRE && child->str == other_child->str
+                                                                               && !other_child->children.empty()
+                                                                               && other_child->children.back()->type == AST_RANGE) {
+                                                                       uses_explicit_size = true;
+                                                                       break;
+                                                               }
+                                                       if (!uses_explicit_size) {
+                                                               AstNode* range = new AstNode();
+                                                               range->type = AST_RANGE;
+                                                               wire->children.push_back(range);
+                                                               range->children.push_back(mkconst_int(0, true));
+                                                               range->children.push_back(mkconst_int(0, true));
+                                                       }
+                                               }
+                                               continue;
+                                       }
                                        AstNode *wire_id = new AstNode(AST_IDENTIFIER);
                                        wire_id->str = wire->str;
                                        AstNode *assign = child->is_input ?
@@ -2764,18 +3395,9 @@ skip_dynamic_range_lvalue_expansion:;
                                }
                        }
 
-               for (auto child : added_mod_children) {
-                       child->replace_ids(prefix, replace_rules);
-                       while (child->simplify(true, false, false, 1, -1, false, false)) { }
-               }
-
                for (auto child : decl->children)
                        if (child->type != AST_WIRE && child->type != AST_MEMORY && child->type != AST_PARAMETER && child->type != AST_LOCALPARAM)
-                       {
-                               AstNode *stmt = child->clone();
-                               stmt->replace_ids(prefix, replace_rules);
-                               new_stmts.push_back(stmt);
-                       }
+                               new_stmts.push_back(child->clone());
 
                new_stmts.insert(new_stmts.end(), output_assignments.begin(), output_assignments.end());
 
@@ -2788,10 +3410,11 @@ skip_dynamic_range_lvalue_expansion:;
                }
 
        replace_fcall_with_id:
+               delete decl;
                if (type == AST_FCALL) {
                        delete_children();
                        type = AST_IDENTIFIER;
-                       str = prefix + str;
+                       str = prefix_id(prefix, "$result");
                }
                if (type == AST_TCALL)
                        str = "";
@@ -2839,7 +3462,14 @@ replace_fcall_later:;
                                if (current_scope[str]->children[0]->isConst())
                                        newNode = current_scope[str]->children[0]->clone();
                        }
-                       else if (at_zero && current_scope.count(str) > 0 && (current_scope[str]->type == AST_WIRE || current_scope[str]->type == AST_AUTOWIRE)) {
+                       else if (at_zero && current_scope.count(str) > 0) {
+                               AstNode *node = current_scope[str];
+                               if (node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY)
+                                       newNode = mkconst_int(0, sign_hint, width_hint);
+                       }
+                       break;
+               case AST_MEMRD:
+                       if (at_zero) {
                                newNode = mkconst_int(0, sign_hint, width_hint);
                        }
                        break;
@@ -2966,6 +3596,7 @@ replace_fcall_later:;
                                }
                        }
                        break;
+               if (0) { case AST_SELFSZ: const_func = RTLIL::const_pos; }
                if (0) { case AST_POS: const_func = RTLIL::const_pos; }
                if (0) { case AST_NEG: const_func = RTLIL::const_neg; }
                        if (children[0]->type == AST_CONSTANT) {
@@ -2974,10 +3605,10 @@ replace_fcall_later:;
                        } else
                        if (children[0]->isConst()) {
                                newNode = new AstNode(AST_REALVALUE);
-                               if (type == AST_POS)
-                                       newNode->realvalue = +children[0]->asReal(sign_hint);
-                               else
+                               if (type == AST_NEG)
                                        newNode->realvalue = -children[0]->asReal(sign_hint);
+                               else
+                                       newNode->realvalue = +children[0]->asReal(sign_hint);
                        }
                        break;
                case AST_TERNARY:
@@ -3042,6 +3673,13 @@ replace_fcall_later:;
                                }
                        }
                        break;
+               case AST_CAST_SIZE:
+                       if (children.at(0)->type == AST_CONSTANT && children.at(1)->type == AST_CONSTANT) {
+                               int width = children[0]->bitsAsConst().as_int();
+                               RTLIL::Const val = children[1]->bitsAsConst(width);
+                               newNode = mkconst_bits(val.bits, children[1]->is_signed);
+                       }
+                       break;
                case AST_CONCAT:
                        string_op = !children.empty();
                        for (auto it = children.begin(); it != children.end(); it++) {
@@ -3087,12 +3725,12 @@ apply_newNode:
        return did_something;
 }
 
-static void replace_result_wire_name_in_function(AstNode *node, std::string &from, std::string &to)
+void AstNode::replace_result_wire_name_in_function(const std::string &from, const std::string &to)
 {
-       for (auto &it : node->children)
-               replace_result_wire_name_in_function(it, from, to);
-       if (node->str == from)
-               node->str = to;
+       for (AstNode *child : children)
+               child->replace_result_wire_name_in_function(from, to);
+       if (str == from && type != AST_FCALL && type != AST_TCALL)
+               str = to;
 }
 
 // replace a readmem[bh] TCALL ast node with a block of memory assignments
@@ -3227,68 +3865,92 @@ AstNode *AstNode::readmem(bool is_readmemh, std::string mem_filename, AstNode *m
        return block;
 }
 
-// annotate the names of all wires and other named objects in a generate block
-void AstNode::expand_genblock(std::string index_var, std::string prefix, std::map<std::string, std::string> &name_map)
+// annotate the names of all wires and other named objects in a named generate
+// or procedural block; nested blocks are themselves annotated such that the
+// prefix is carried forward, but resolution of their children is deferred
+void AstNode::expand_genblock(const std::string &prefix)
 {
-       if (!index_var.empty() && type == AST_IDENTIFIER && str == index_var) {
-               if (children.empty()) {
-                       current_scope[index_var]->children[0]->cloneInto(this);
-               } else {
-                       AstNode *p = new AstNode(AST_LOCALPARAM, current_scope[index_var]->children[0]->clone());
-                       p->str = stringf("$genval$%d", autoidx++);
-                       current_ast_mod->children.push_back(p);
-                       str = p->str;
-                       id2ast = p;
+       if (type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE) {
+               log_assert(!str.empty());
+
+               // search starting in the innermost scope and then stepping outward
+               for (size_t ppos = prefix.size() - 1; ppos; --ppos) {
+                       if (prefix.at(ppos) != '.') continue;
+
+                       std::string new_prefix = prefix.substr(0, ppos + 1);
+                       auto attempt_resolve = [&new_prefix](const std::string &ident) -> std::string {
+                               std::string new_name = prefix_id(new_prefix, ident);
+                               if (current_scope.count(new_name))
+                                       return new_name;
+                               return {};
+                       };
+
+                       // attempt to resolve the full identifier
+                       std::string resolved = attempt_resolve(str);
+                       if (!resolved.empty()) {
+                               str = resolved;
+                               break;
+                       }
+
+                       // attempt to resolve hierarchical prefixes within the identifier,
+                       // as the prefix could refer to a local scope which exists but
+                       // hasn't yet been elaborated
+                       for (size_t spos = str.size() - 1; spos; --spos) {
+                               if (str.at(spos) != '.') continue;
+                               resolved = attempt_resolve(str.substr(0, spos));
+                               if (!resolved.empty()) {
+                                       str = resolved + str.substr(spos);
+                                       ppos = 1; // break outer loop
+                                       break;
+                               }
+                       }
+
                }
        }
 
-       if ((type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE) && name_map.count(str) > 0)
-               str = name_map[str];
-
-       std::map<std::string, std::string> backup_name_map;
+       auto prefix_node = [&prefix](AstNode* child) {
+               if (child->str.empty()) return;
+               std::string new_name = prefix_id(prefix, child->str);
+               if (child->type == AST_FUNCTION)
+                       child->replace_result_wire_name_in_function(child->str, new_name);
+               else
+                       child->str = new_name;
+               current_scope[new_name] = child;
+       };
 
        for (size_t i = 0; i < children.size(); i++) {
                AstNode *child = children[i];
-               if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER || child->type == AST_LOCALPARAM ||
-                               child->type == AST_FUNCTION || child->type == AST_TASK || child->type == AST_CELL || child->type == AST_TYPEDEF || child->type == AST_ENUM_ITEM) {
-                       if (backup_name_map.size() == 0)
-                               backup_name_map = name_map;
-                       std::string new_name = prefix[0] == '\\' ? prefix.substr(1) : prefix;
-                       size_t pos = child->str.rfind('.');
-                       if (pos == std::string::npos)
-                               pos = child->str[0] == '\\' && prefix[0] == '\\' ? 1 : 0;
-                       else
-                               pos = pos + 1;
-                       new_name = child->str.substr(0, pos) + new_name + child->str.substr(pos);
-                       if (new_name[0] != '$' && new_name[0] != '\\')
-                               new_name = prefix[0] + new_name;
-                       name_map[child->str] = new_name;
-                       if (child->type == AST_FUNCTION)
-                               replace_result_wire_name_in_function(child, child->str, new_name);
-                       else
-                               child->str = new_name;
-                       current_scope[new_name] = child;
-               }
-               if (child->type == AST_ENUM){
+
+               switch (child->type) {
+               case AST_WIRE:
+               case AST_MEMORY:
+               case AST_PARAMETER:
+               case AST_LOCALPARAM:
+               case AST_FUNCTION:
+               case AST_TASK:
+               case AST_CELL:
+               case AST_TYPEDEF:
+               case AST_ENUM_ITEM:
+               case AST_GENVAR:
+                       prefix_node(child);
+                       break;
+
+               case AST_BLOCK:
+               case AST_GENBLOCK:
+                       if (!child->str.empty())
+                               prefix_node(child);
+                       break;
+
+               case AST_ENUM:
                        current_scope[child->str] = child;
                        for (auto enode : child->children){
                                log_assert(enode->type == AST_ENUM_ITEM);
-                               if (backup_name_map.size() == 0)
-                                       backup_name_map = name_map;
-                               std::string new_name = prefix[0] == '\\' ? prefix.substr(1) : prefix;
-                               size_t pos = enode->str.rfind('.');
-                               if (pos == std::string::npos)
-                                       pos = enode->str[0] == '\\' && prefix[0] == '\\' ? 1 : 0;
-                               else
-                                       pos = pos + 1;
-                               new_name = enode->str.substr(0, pos) + new_name + enode->str.substr(pos);
-                               if (new_name[0] != '$' && new_name[0] != '\\')
-                                       new_name = prefix[0] + new_name;
-                               name_map[enode->str] = new_name;
-
-                               enode->str = new_name;
-                               current_scope[new_name] = enode;
+                               prefix_node(enode);
                        }
+                       break;
+
+               default:
+                       break;
                }
        }
 
@@ -3298,39 +3960,57 @@ void AstNode::expand_genblock(std::string index_var, std::string prefix, std::ma
                // still needs to recursed-into
                if (type == AST_PREFIX && i == 1 && child->type == AST_IDENTIFIER)
                        continue;
-               if (child->type != AST_FUNCTION && child->type != AST_TASK)
-                       child->expand_genblock(index_var, prefix, name_map);
-       }
-
+               // functions/tasks may reference wires, constants, etc. in this scope
+               if (child->type == AST_FUNCTION || child->type == AST_TASK)
+                       continue;
+               // named blocks pick up the current prefix and will expanded later
+               if ((child->type == AST_GENBLOCK || child->type == AST_BLOCK) && !child->str.empty())
+                       continue;
 
-       if (backup_name_map.size() > 0)
-               name_map.swap(backup_name_map);
+               child->expand_genblock(prefix);
+       }
 }
 
-// rename stuff (used when tasks of functions are instantiated)
-void AstNode::replace_ids(const std::string &prefix, const std::map<std::string, std::string> &rules)
+// add implicit AST_GENBLOCK names according to IEEE 1364-2005 Section 12.4.3 or
+// IEEE 1800-2017 Section 27.6
+void AstNode::label_genblks(std::set<std::string>& existing, int &counter)
 {
-       if (type == AST_BLOCK)
-       {
-               std::map<std::string, std::string> new_rules = rules;
-               std::string new_prefix = prefix + str;
-
-               for (auto child : children)
-                       if (child->type == AST_WIRE) {
-                               new_rules[child->str] = new_prefix + child->str;
-                               child->str = new_prefix + child->str;
-                       }
+       switch (type) {
+       case AST_GENIF:
+       case AST_GENFOR:
+       case AST_GENCASE:
+               // seeing a proper generate control flow construct increments the
+               // counter once
+               ++counter;
+               for (AstNode *child : children)
+                       child->label_genblks(existing, counter);
+               break;
 
-               for (auto child : children)
-                       if (child->type != AST_WIRE)
-                               child->replace_ids(new_prefix, new_rules);
+       case AST_GENBLOCK: {
+               // if this block is unlabeled, generate its corresponding unique name
+               for (int padding = 0; str.empty(); ++padding) {
+                       std::string candidate = "\\genblk";
+                       for (int i = 0; i < padding; ++i)
+                               candidate += '0';
+                       candidate += std::to_string(counter);
+                       if (!existing.count(candidate))
+                               str = candidate;
+               }
+               // within a genblk, the counter starts fresh
+               std::set<std::string> existing_local = existing;
+               int counter_local = 0;
+               for (AstNode *child : children)
+                       child->label_genblks(existing_local, counter_local);
+               break;
        }
-       else
-       {
-               if (type == AST_IDENTIFIER && rules.count(str) > 0)
-                       str = rules.at(str);
-               for (auto child : children)
-                       child->replace_ids(prefix, rules);
+
+       default:
+               // track names which could conflict with implicit genblk names
+               if (str.rfind("\\genblk", 0) == 0)
+                       existing.insert(str);
+               for (AstNode *child : children)
+                       child->label_genblks(existing, counter);
+               break;
        }
 }
 
@@ -3419,8 +4099,8 @@ void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg
                }
        }
 
-       // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg'
-       if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !is_reg))
+       // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg' or 'logic'
+       if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !(is_reg || is_logic)))
                mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED;
 
        if (type == AST_MODULE && get_bool_attribute(ID::mem2reg))
@@ -3766,17 +4446,89 @@ void AstNode::meminfo(int &mem_width, int &mem_size, int &addr_bits)
                addr_bits++;
 }
 
+bool AstNode::detect_latch(const std::string &var)
+{
+       switch (type)
+       {
+       case AST_ALWAYS:
+               for (auto &c : children)
+               {
+                       switch (c->type)
+                       {
+                       case AST_POSEDGE:
+                       case AST_NEGEDGE:
+                               return false;
+                       case AST_EDGE:
+                               break;
+                       case AST_BLOCK:
+                               if (!c->detect_latch(var))
+                                       return false;
+                               break;
+                       default:
+                               log_abort();
+                       }
+               }
+               return true;
+       case AST_BLOCK:
+               for (auto &c : children)
+                       if (!c->detect_latch(var))
+                               return false;
+               return true;
+       case AST_CASE:
+               {
+                       bool r = true;
+                       for (auto &c : children) {
+                               if (c->type == AST_COND) {
+                                       if (c->children.at(1)->detect_latch(var))
+                                               return true;
+                                       r = false;
+                               }
+                               if (c->type == AST_DEFAULT) {
+                                       if (c->children.at(0)->detect_latch(var))
+                                               return true;
+                                       r = false;
+                               }
+                       }
+                       return r;
+               }
+       case AST_ASSIGN_EQ:
+       case AST_ASSIGN_LE:
+               if (children.at(0)->type == AST_IDENTIFIER &&
+                               children.at(0)->children.empty() && children.at(0)->str == var)
+                       return false;
+               return true;
+       default:
+               return true;
+       }
+}
+
 bool AstNode::has_const_only_constructs(bool &recommend_const_eval)
 {
+       std::set<std::string> visited;
+       return has_const_only_constructs(visited, recommend_const_eval);
+}
+
+bool AstNode::has_const_only_constructs(std::set<std::string>& visited, bool &recommend_const_eval)
+{
+       if (type == AST_FUNCTION || type == AST_TASK)
+       {
+               if (visited.count(str))
+               {
+                       recommend_const_eval = true;
+                       return false;
+               }
+               visited.insert(str);
+       }
+
        if (type == AST_FOR)
                recommend_const_eval = true;
        if (type == AST_WHILE || type == AST_REPEAT)
                return true;
        if (type == AST_FCALL && current_scope.count(str))
-               if (current_scope[str]->has_const_only_constructs(recommend_const_eval))
+               if (current_scope[str]->has_const_only_constructs(visited, recommend_const_eval))
                        return true;
        for (auto child : children)
-               if (child->AstNode::has_const_only_constructs(recommend_const_eval))
+               if (child->AstNode::has_const_only_constructs(visited, recommend_const_eval))
                        return true;
        return false;
 }
@@ -3831,27 +4583,9 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
        size_t argidx = 0;
        for (auto child : children)
        {
-               if (child->type == AST_WIRE)
-               {
-                       while (child->simplify(true, false, false, 1, -1, false, true)) { }
-                       if (!child->range_valid)
-                               log_file_error(child->filename, child->location.first_line, "Can't determine size of variable %s\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               child->str.c_str(), fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
-                       variables[child->str].val = RTLIL::Const(RTLIL::State::Sx, abs(child->range_left - child->range_right)+1);
-                       variables[child->str].offset = min(child->range_left, child->range_right);
-                       variables[child->str].is_signed = child->is_signed;
-                       if (child->is_input && argidx < fcall->children.size())
-                               variables[child->str].val = fcall->children.at(argidx++)->bitsAsConst(variables[child->str].val.bits.size());
-                       backup_scope[child->str] = current_scope[child->str];
-                       current_scope[child->str] = child;
-                       continue;
-               }
-
                block->children.push_back(child->clone());
        }
 
-       log_assert(variables.count(str) != 0);
-
        while (!block->children.empty())
        {
                AstNode *stmt = block->children.front();
@@ -3863,6 +4597,47 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                stmt->dumpAst(NULL, "stmt> ");
 #endif
 
+               if (stmt->type == AST_WIRE)
+               {
+                       while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
+                       if (!stmt->range_valid)
+                               log_file_error(stmt->filename, stmt->location.first_line, "Can't determine size of variable %s\n%s:%d.%d-%d.%d: ... called from here.\n",
+                                               stmt->str.c_str(), fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       variables[stmt->str].val = RTLIL::Const(RTLIL::State::Sx, abs(stmt->range_left - stmt->range_right)+1);
+                       variables[stmt->str].offset = min(stmt->range_left, stmt->range_right);
+                       variables[stmt->str].is_signed = stmt->is_signed;
+                       if (stmt->is_input && argidx < fcall->children.size()) {
+                               int width = variables[stmt->str].val.bits.size();
+                               auto* arg_node = fcall->children.at(argidx++);
+                               if (arg_node->type == AST_CONSTANT) {
+                                       variables[stmt->str].val = arg_node->bitsAsConst(width);
+                               } else {
+                                       log_assert(arg_node->type == AST_REALVALUE);
+                                       variables[stmt->str].val = arg_node->realAsConst(width);
+                               }
+                       }
+                       if (!backup_scope.count(stmt->str))
+                               backup_scope[stmt->str] = current_scope[stmt->str];
+                       current_scope[stmt->str] = stmt;
+
+                       block->children.erase(block->children.begin());
+                       continue;
+               }
+
+               log_assert(variables.count(str) != 0);
+
+               if (stmt->type == AST_LOCALPARAM)
+               {
+                       while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
+
+                       if (!backup_scope.count(stmt->str))
+                               backup_scope[stmt->str] = current_scope[stmt->str];
+                       current_scope[stmt->str] = stmt;
+
+                       block->children.erase(block->children.begin());
+                       continue;
+               }
+
                if (stmt->type == AST_ASSIGN_EQ)
                {
                        if (stmt->children.at(0)->type == AST_IDENTIFIER && stmt->children.at(0)->children.size() != 0 &&
@@ -4005,6 +4780,9 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
 
                if (stmt->type == AST_BLOCK)
                {
+                       if (!stmt->str.empty())
+                               stmt->expand_genblock(stmt->str + ".");
+
                        block->children.erase(block->children.begin());
                        block->children.insert(block->children.begin(), stmt->children.begin(), stmt->children.end());
                        stmt->children.clear();
@@ -4034,7 +4812,7 @@ void AstNode::allocateDefaultEnumValues()
        int last_enum_int = -1;
        for (auto node : children) {
                log_assert(node->type==AST_ENUM_ITEM);
-               node->attributes["\\enum_base_type"] = mkconst_str(str);
+               node->attributes[ID::enum_base_type] = mkconst_str(str);
                for (size_t i = 0; i < node->children.size(); i++) {
                        switch (node->children[i]->type) {
                        case AST_NONE: