verilog: significant block scoping improvements
[yosys.git] / frontends / ast / simplify.cc
index c9da8ba4886a7e966b802382c872da312a9005ca..77911e96620b23742e8f05997246c8a4fb836105 100644 (file)
@@ -89,7 +89,7 @@ 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;
                                        YS_FALLTHROUGH
                                case 'x':
@@ -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();
                        }
@@ -383,43 +394,9 @@ static AstNode *node_int(int ival)
        return AstNode::mkconst_int(ival, true);
 }
 
-static AstNode *node_uint(uint ival)
-{
-       return AstNode::mkconst_int(ival, false);
-}
-
-static unsigned int power_of_two(int n)
-{
-       // iff n is a power of two then return the power, else return 0
-       // caller must ensure n > 1
-       log_assert(n > 1);
-       if (n & (n - 1)) {
-               // not a power of 2
-               return 0;
-       }
-       // brute force the shift
-       for (unsigned int i = 1; i < 32; i++) {
-               n >>= 1;
-               if (n & 1) {
-                       return i;
-               }
-       }
-       return 0;
-}
-
 static AstNode *multiply_by_const(AstNode *expr_node, int stride)
 {
-       // the stride is very likely a power of 2, e.g. 8 for bytes
-       // and so could be optimised with a shift
-       AstNode *node;
-       unsigned int shift;
-       if ((shift = power_of_two(stride)) > 0) {
-               node = new AstNode(AST_SHIFT_LEFT, expr_node, node_uint(shift));
-       }
-       else {
-               node = new AstNode(AST_MUL, expr_node, node_int(stride));
-       }
-       return node;
+       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)
@@ -551,6 +528,37 @@ static AstNode *make_packed_struct(AstNode *template_node, std::string &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().
@@ -750,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];
@@ -846,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;
@@ -1018,6 +1029,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        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:
@@ -1194,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))
@@ -1202,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)
@@ -1322,6 +1343,9 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        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;
                        }
@@ -1501,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];
@@ -1518,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();
@@ -1605,6 +1632,13 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        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) {
@@ -1709,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;
        }
@@ -1750,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();
@@ -1780,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) {
@@ -1821,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;
@@ -1831,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 {
@@ -1867,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;
@@ -1891,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++)
@@ -1918,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]);
                }
 
@@ -1955,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]);
                        }
 
@@ -2010,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> ");
@@ -2034,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]);
                        }
 
@@ -2204,6 +2245,9 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                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
@@ -2816,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?
@@ -2848,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
@@ -2860,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;
                        }
 
@@ -3076,12 +3176,16 @@ 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(ID::via_celltype))
@@ -3089,14 +3193,16 @@ skip_dynamic_range_lvalue_expansion:;
                        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;
                        }
 
@@ -3107,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;
@@ -3118,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)) { }
 
@@ -3171,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;
@@ -3214,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:
@@ -3232,7 +3337,6 @@ 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;
@@ -3243,19 +3347,41 @@ skip_dynamic_range_lvalue_expansion:;
 
                                        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 ?
@@ -3269,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());
 
@@ -3293,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 = "";
@@ -3344,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;
@@ -3548,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++) {
@@ -3593,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
@@ -3733,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;
                }
        }
 
@@ -3804,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;
        }
 }
 
@@ -4272,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;
 }
@@ -4337,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();
@@ -4369,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 &&
@@ -4511,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();