Extend simplify() recursion warning
[yosys.git] / frontends / ast / simplify.cc
index fc2976c83e8833a7f9d22496dbfe1d8b218cf774..babd8c562f4d253ed6c3daa2fc97158617f033d2 100644 (file)
@@ -549,6 +549,16 @@ static bool node_contains_assignment_to(const AstNode* node, const AstNode* var)
        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().
@@ -561,10 +571,12 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        static bool deep_recursion_warning = false;
 
        if (recursion_counter++ == 1000 && deep_recursion_warning) {
-               log_warning("Deep recursion in AST simplifier.\nDoes this design contain insanely long expressions?\n");
+               log_warning("Deep recursion in AST simplifier.\nDoes this design contain overly long or deeply nested expressions, or excessive recursion?\n");
                deep_recursion_warning = false;
        }
 
+       static bool unevaluated_tern_branch = false;
+
        AstNode *newNode = NULL;
        bool did_something = false;
 
@@ -748,6 +760,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];
@@ -928,7 +943,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && children[0]->id2ast->is_logic)
                                children[0]->id2ast->is_reg = true; // if logic type is used in a block asignment
                        if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && !children[0]->id2ast->is_reg)
-                               log_warning("wire '%s' is assigned in a block at %s:%d.%d-%d.%d.\n", children[0]->str.c_str(), filename.c_str(), location.first_line, location.first_column, location.last_line, location.last_column);
+                               log_warning("wire '%s' is assigned in a block at %s.\n", children[0]->str.c_str(), loc_string().c_str());
                        if (type == AST_ASSIGN && children[0]->id2ast->is_reg) {
                                bool is_rand_reg = false;
                                if (children[1]->type == AST_FCALL) {
@@ -942,7 +957,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                                                is_rand_reg = true;
                                }
                                if (!is_rand_reg)
-                                       log_warning("reg '%s' is assigned in a continuous assignment at %s:%d.%d-%d.%d.\n", children[0]->str.c_str(), filename.c_str(), location.first_line, location.first_column, location.last_line, location.last_column);
+                                       log_warning("reg '%s' is assigned in a continuous assignment at %s.\n", children[0]->str.c_str(), loc_string().c_str());
                        }
                        children[0]->was_checked = true;
                }
@@ -1078,7 +1093,6 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                break;
 
        case AST_TERNARY:
-               detect_width_simple = true;
                child_0_is_self_determined = true;
                break;
 
@@ -1111,6 +1125,24 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                detectSignWidth(width_hint, sign_hint);
 
        if (type == AST_TERNARY) {
+               if (width_hint < 0) {
+                       while (!children[0]->basic_prep && children[0]->simplify(true, false, in_lvalue, stage, -1, false, in_param))
+                               did_something = true;
+
+                       bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
+                       AstNode *chosen = get_tern_choice().first;
+
+                       unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[2];
+                       while (!children[1]->basic_prep && children[1]->simplify(false, false, in_lvalue, stage, -1, false, in_param))
+                               did_something = true;
+
+                       unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[1];
+                       while (!children[2]->basic_prep && children[2]->simplify(false, false, in_lvalue, stage, -1, false, in_param))
+                               did_something = true;
+
+                       unevaluated_tern_branch = backup_unevaluated_tern_branch;
+                       detectSignWidth(width_hint, sign_hint);
+               }
                int width_hint_left, width_hint_right;
                bool sign_hint_left, sign_hint_right;
                bool found_real_left, found_real_right;
@@ -1174,6 +1206,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        for (size_t i = 0; i < children.size(); i++) {
                bool did_something_here = true;
                bool backup_flag_autowire = flag_autowire;
+               bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
                if ((type == AST_GENFOR || type == AST_FOR) && i >= 3)
                        break;
                if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1)
@@ -1186,6 +1219,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        break;
                if (type == AST_DEFPARAM && i == 0)
                        flag_autowire = true;
+               if (type == AST_TERNARY && i > 0 && !unevaluated_tern_branch) {
+                       AstNode *chosen = get_tern_choice().first;
+                       unevaluated_tern_branch = chosen && chosen != children[i];
+               }
                while (did_something_here && i < children.size()) {
                        bool const_fold_here = const_fold, in_lvalue_here = in_lvalue;
                        int width_hint_here = width_hint;
@@ -1205,11 +1242,6 @@ 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)
@@ -1230,6 +1262,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        did_something = true;
                }
                flag_autowire = backup_flag_autowire;
+               unevaluated_tern_branch = backup_unevaluated_tern_branch;
        }
        for (auto &attr : attributes) {
                while (attr.second->simplify(true, false, false, stage, -1, false, true))
@@ -1855,19 +1888,24 @@ 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++) {
@@ -1915,14 +1953,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++)
@@ -1942,8 +1982,7 @@ 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++) {
@@ -1979,8 +2018,7 @@ 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++) {
@@ -2058,8 +2096,7 @@ 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++) {
@@ -3159,16 +3196,21 @@ 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();
 
-               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))
+               AstNode *decl = current_scope[str];
+               if (unevaluated_tern_branch && decl->is_recursive_function())
+                       goto replace_fcall_later;
+               decl = decl->clone();
+               decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion
+               decl->expand_genblock(prefix);
+
+               if (decl->type == AST_FUNCTION && !decl->attributes.count(ID::via_celltype))
                {
+                       bool require_const_eval = decl->has_const_only_constructs();
                        bool all_args_const = true;
                        for (auto child : children) {
                                while (child->simplify(true, false, false, 1, -1, false, true)) { }
@@ -3177,12 +3219,14 @@ skip_dynamic_range_lvalue_expansion:;
                        }
 
                        if (all_args_const) {
-                               AstNode *func_workspace = current_scope[str]->clone();
-                               func_workspace->str = NEW_ID.str();
-                               func_workspace->replace_result_wire_name_in_function(str, func_workspace->str);
-                               newNode = func_workspace->eval_const_function(this);
+                               AstNode *func_workspace = decl->clone();
+                               func_workspace->str = prefix_id(prefix, "$result");
+                               newNode = func_workspace->eval_const_function(this, in_param || require_const_eval);
                                delete func_workspace;
-                               goto apply_newNode;
+                               if (newNode) {
+                                       delete decl;
+                                       goto apply_newNode;
+                               }
                        }
 
                        if (in_param)
@@ -3192,8 +3236,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;
@@ -3203,16 +3245,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)) { }
 
@@ -3256,7 +3299,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;
@@ -3318,7 +3360,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;
@@ -3329,15 +3370,11 @@ 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())
                                {
@@ -3366,6 +3403,8 @@ skip_dynamic_range_lvalue_expansion:;
                                                                range->children.push_back(mkconst_int(0, true));
                                                        }
                                                }
+                                               // updates the sizing
+                                               while (wire->simplify(true, false, false, 1, -1, false, false)) { }
                                                continue;
                                        }
                                        AstNode *wire_id = new AstNode(AST_IDENTIFIER);
@@ -3381,18 +3420,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());
 
@@ -3405,10 +3435,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 = "";
@@ -3608,24 +3639,9 @@ replace_fcall_later:;
                case AST_TERNARY:
                        if (children[0]->isConst())
                        {
-                               bool found_sure_true = false;
-                               bool found_maybe_true = false;
-
-                               if (children[0]->type == AST_CONSTANT)
-                                       for (auto &bit : children[0]->bits) {
-                                               if (bit == RTLIL::State::S1)
-                                                       found_sure_true = true;
-                                               if (bit > RTLIL::State::S1)
-                                                       found_maybe_true = true;
-                                       }
-                               else
-                                       found_sure_true = children[0]->asReal(sign_hint) != 0;
-
-                               AstNode *choice = NULL, *not_choice = NULL;
-                               if (found_sure_true)
-                                       choice = children[1], not_choice = children[2];
-                               else if (!found_maybe_true)
-                                       choice = children[2], not_choice = children[1];
+                               auto pair = get_tern_choice();
+                               AstNode *choice = pair.first;
+                               AstNode *not_choice = pair.second;
 
                                if (choice != NULL) {
                                        if (choice->type == AST_CONSTANT) {
@@ -3859,63 +3875,52 @@ 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, bool original_scope)
+// 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)
 {
-       // `original_scope` defaults to false, and is used to prevent the premature
-       // prefixing of items in named sub-blocks
-
-       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) {
-               if (name_map.count(str) > 0) {
-                       str = name_map[str];
-               } else {
-                       // remap the prefix of this ident if it is a local generate scope
-                       size_t pos = str.rfind('.');
-                       if (pos != std::string::npos) {
-                               std::string existing_prefix = str.substr(0, pos);
-                               if (name_map.count(existing_prefix) > 0) {
-                                       str = name_map[existing_prefix] + str.substr(pos);
-                               }
+               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;
                        }
-               }
-       }
-
-       std::map<std::string, std::string> backup_name_map;
 
-       auto prefix_node = [&](AstNode* child) {
-               if (backup_name_map.size() == 0)
-                       backup_name_map = name_map;
+                       // 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 within a nested scope
-               if (!original_scope) {
-                       // this declaration shadows anything in the parent scope(s)
-                       name_map[child->str] = child->str;
-                       return;
                }
+       }
 
-               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;
+       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
@@ -3967,43 +3972,55 @@ void AstNode::expand_genblock(std::string index_var, std::string prefix, std::ma
                        continue;
                // functions/tasks may reference wires, constants, etc. in this scope
                if (child->type == AST_FUNCTION || child->type == AST_TASK)
-                       child->expand_genblock(index_var, prefix, name_map, false);
-               // continue prefixing if this child block is anonymous
-               else if (child->type == AST_GENBLOCK || child->type == AST_BLOCK)
-                       child->expand_genblock(index_var, prefix, name_map, original_scope && child->str.empty());
-               else
-                       child->expand_genblock(index_var, prefix, name_map, original_scope);
-       }
-
+                       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;
        }
 }
 
@@ -4495,33 +4512,12 @@ bool AstNode::detect_latch(const std::string &var)
        }
 }
 
-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)
+bool AstNode::has_const_only_constructs()
 {
-       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(visited, recommend_const_eval))
-                       return true;
        for (auto child : children)
-               if (child->AstNode::has_const_only_constructs(visited, recommend_const_eval))
+               if (child->has_const_only_constructs())
                        return true;
        return false;
 }
@@ -4537,19 +4533,26 @@ bool AstNode::is_simple_const_expr()
 }
 
 // helper function for AstNode::eval_const_function()
-void AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &variables, AstNode *fcall)
+bool AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &variables, AstNode *fcall, bool must_succeed)
 {
        if (type == AST_IDENTIFIER && variables.count(str)) {
                int offset = variables.at(str).offset, width = variables.at(str).val.bits.size();
                if (!children.empty()) {
-                       if (children.size() != 1 || children.at(0)->type != AST_RANGE)
-                               log_file_error(filename, location.first_line, "Memory access in constant function is not supported\n%s:%d.%d-%d.%d: ...called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
-                       children.at(0)->replace_variables(variables, fcall);
+                       if (children.size() != 1 || children.at(0)->type != AST_RANGE) {
+                               if (!must_succeed)
+                                       return false;
+                               log_file_error(filename, location.first_line, "Memory access in constant function is not supported\n%s: ...called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
+                       if (!children.at(0)->replace_variables(variables, fcall, must_succeed))
+                               return false;
                        while (simplify(true, false, false, 1, -1, false, true)) { }
-                       if (!children.at(0)->range_valid)
-                               log_file_error(filename, location.first_line, "Non-constant range\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (!children.at(0)->range_valid) {
+                               if (!must_succeed)
+                                       return false;
+                               log_file_error(filename, location.first_line, "Non-constant range\n%s: ... called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
                        offset = min(children.at(0)->range_left, children.at(0)->range_right);
                        width = min(std::abs(children.at(0)->range_left - children.at(0)->range_right) + 1, width);
                }
@@ -4559,19 +4562,22 @@ void AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &varia
                AstNode *newNode = mkconst_bits(new_bits, variables.at(str).is_signed);
                newNode->cloneInto(this);
                delete newNode;
-               return;
+               return true;
        }
 
        for (auto &child : children)
-               child->replace_variables(variables, fcall);
+               if (!child->replace_variables(variables, fcall, must_succeed))
+                       return false;
+       return true;
 }
 
-// evaluate functions with all-const arguments
-AstNode *AstNode::eval_const_function(AstNode *fcall)
+// attempt to statically evaluate a functions with all-const arguments
+AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed)
 {
-       std::map<std::string, AstNode*> backup_scope;
+       std::map<std::string, AstNode*> backup_scope = current_scope;
        std::map<std::string, AstNode::varinfo_t> variables;
        AstNode *block = new AstNode(AST_BLOCK);
+       AstNode *result = nullptr;
 
        size_t argidx = 0;
        for (auto child : children)
@@ -4593,24 +4599,37 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                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->range_valid) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Can't determine size of variable %s\n%s: ... called from here.\n",
+                                               stmt->str.c_str(), fcall->loc_string().c_str());
+                       }
+                       AstNode::varinfo_t &variable = variables[stmt->str];
+                       int width = abs(stmt->range_left - stmt->range_right) + 1;
+                       // if this variable has already been declared as an input, check the
+                       // sizes match if it already had an explicit size
+                       if (variable.arg && variable.explicitly_sized && variable.val.size() != width) {
+                               log_file_error(filename, location.first_line, "Incompatible re-declaration of constant function wire %s.\n", stmt->str.c_str());
+                       }
+                       variable.val = RTLIL::Const(RTLIL::State::Sx, width);
+                       variable.offset = min(stmt->range_left, stmt->range_right);
+                       variable.is_signed = stmt->is_signed;
+                       variable.explicitly_sized = stmt->children.size() &&
+                               stmt->children.back()->type == AST_RANGE;
+                       // identify the argument corresponding to this wire, if applicable
                        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);
+                               variable.arg = fcall->children.at(argidx++);
+                       }
+                       // load the constant arg's value into this variable
+                       if (variable.arg) {
+                               if (variable.arg->type == AST_CONSTANT) {
+                                       variable.val = variable.arg->bitsAsConst(width);
                                } else {
-                                       log_assert(arg_node->type == AST_REALVALUE);
-                                       variables[stmt->str].val = arg_node->realAsConst(width);
+                                       log_assert(variable.arg->type == AST_REALVALUE);
+                                       variable.val = variable.arg->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());
@@ -4623,8 +4642,6 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                {
                        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());
@@ -4635,32 +4652,46 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                {
                        if (stmt->children.at(0)->type == AST_IDENTIFIER && stmt->children.at(0)->children.size() != 0 &&
                                        stmt->children.at(0)->children.at(0)->type == AST_RANGE)
-                               stmt->children.at(0)->children.at(0)->replace_variables(variables, fcall);
-                       stmt->children.at(1)->replace_variables(variables, fcall);
+                               if (!stmt->children.at(0)->children.at(0)->replace_variables(variables, fcall, must_succeed))
+                                       goto finished;
+                       if (!stmt->children.at(1)->replace_variables(variables, fcall, must_succeed))
+                               goto finished;
                        while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
 
                        if (stmt->type != AST_ASSIGN_EQ)
                                continue;
 
-                       if (stmt->children.at(1)->type != AST_CONSTANT)
-                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s:%d.%d-%d.%d: ... called from here. X\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (stmt->children.at(1)->type != AST_CONSTANT) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here. X\n",
+                                               fcall->loc_string().c_str());
+                       }
 
-                       if (stmt->children.at(0)->type != AST_IDENTIFIER)
-                               log_file_error(stmt->filename, stmt->location.first_line, "Unsupported composite left hand side in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (stmt->children.at(0)->type != AST_IDENTIFIER) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Unsupported composite left hand side in constant function\n%s: ... called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
 
-                       if (!variables.count(stmt->children.at(0)->str))
-                               log_file_error(stmt->filename, stmt->location.first_line, "Assignment to non-local variable in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (!variables.count(stmt->children.at(0)->str)) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Assignment to non-local variable in constant function\n%s: ... called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
 
                        if (stmt->children.at(0)->children.empty()) {
                                variables[stmt->children.at(0)->str].val = stmt->children.at(1)->bitsAsConst(variables[stmt->children.at(0)->str].val.bits.size());
                        } else {
                                AstNode *range = stmt->children.at(0)->children.at(0);
-                               if (!range->range_valid)
-                                       log_file_error(range->filename, range->location.first_line, "Non-constant range\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                                       fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                               if (!range->range_valid) {
+                                       if (!must_succeed)
+                                               goto finished;
+                                       log_file_error(range->filename, range->location.first_line, "Non-constant range\n%s: ... called from here.\n",
+                                                       fcall->loc_string().c_str());
+                               }
                                int offset = min(range->range_left, range->range_right);
                                int width = std::abs(range->range_left - range->range_right) + 1;
                                varinfo_t &v = variables[stmt->children.at(0)->str];
@@ -4687,12 +4718,16 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                if (stmt->type == AST_WHILE)
                {
                        AstNode *cond = stmt->children.at(0)->clone();
-                       cond->replace_variables(variables, fcall);
+                       if (!cond->replace_variables(variables, fcall, must_succeed))
+                               goto finished;
                        while (cond->simplify(true, false, false, 1, -1, false, true)) { }
 
-                       if (cond->type != AST_CONSTANT)
-                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (cond->type != AST_CONSTANT) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
 
                        if (cond->asBool()) {
                                block->children.insert(block->children.begin(), stmt->children.at(1)->clone());
@@ -4708,12 +4743,16 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                if (stmt->type == AST_REPEAT)
                {
                        AstNode *num = stmt->children.at(0)->clone();
-                       num->replace_variables(variables, fcall);
+                       if (!num->replace_variables(variables, fcall, must_succeed))
+                               goto finished;
                        while (num->simplify(true, false, false, 1, -1, false, true)) { }
 
-                       if (num->type != AST_CONSTANT)
-                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                       if (num->type != AST_CONSTANT) {
+                               if (!must_succeed)
+                                       goto finished;
+                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
+                                               fcall->loc_string().c_str());
+                       }
 
                        block->children.erase(block->children.begin());
                        for (int i = 0; i < num->bitsAsConst().as_int(); i++)
@@ -4727,7 +4766,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                if (stmt->type == AST_CASE)
                {
                        AstNode *expr = stmt->children.at(0)->clone();
-                       expr->replace_variables(variables, fcall);
+                       if (!expr->replace_variables(variables, fcall, must_succeed))
+                               goto finished;
                        while (expr->simplify(true, false, false, 1, -1, false, true)) { }
 
                        AstNode *sel_case = NULL;
@@ -4744,14 +4784,18 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                                for (size_t j = 0; j+1 < stmt->children.at(i)->children.size() && !found_match; j++)
                                {
                                        AstNode *cond = stmt->children.at(i)->children.at(j)->clone();
-                                       cond->replace_variables(variables, fcall);
+                                       if (!cond->replace_variables(variables, fcall, must_succeed))
+                                               goto finished;
 
                                        cond = new AstNode(AST_EQ, expr->clone(), cond);
                                        while (cond->simplify(true, false, false, 1, -1, false, true)) { }
 
-                                       if (cond->type != AST_CONSTANT)
-                                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                                                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+                                       if (cond->type != AST_CONSTANT) {
+                                               if (!must_succeed)
+                                                       goto finished;
+                                               log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
+                                                               fcall->loc_string().c_str());
+                                       }
 
                                        found_match = cond->asBool();
                                        delete cond;
@@ -4773,6 +4817,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();
@@ -4780,20 +4827,20 @@ AstNode *AstNode::eval_const_function(AstNode *fcall)
                        continue;
                }
 
-               log_file_error(stmt->filename, stmt->location.first_line, "Unsupported language construct in constant function\n%s:%d.%d-%d.%d: ... called from here.\n",
-                               fcall->filename.c_str(), fcall->location.first_line, fcall->location.first_column, fcall->location.last_line, fcall->location.last_column);
+               if (!must_succeed)
+                       goto finished;
+               log_file_error(stmt->filename, stmt->location.first_line, "Unsupported language construct in constant function\n%s: ... called from here.\n",
+                               fcall->loc_string().c_str());
                log_abort();
        }
 
-       delete block;
+       result = AstNode::mkconst_bits(variables.at(str).val.bits, variables.at(str).is_signed);
 
-       for (auto &it : backup_scope)
-               if (it.second == NULL)
-                       current_scope.erase(it.first);
-               else
-                       current_scope[it.first] = it.second;
+finished:
+       delete block;
+       current_scope = backup_scope;
 
-       return AstNode::mkconst_bits(variables.at(str).val.bits, variables.at(str).is_signed);
+       return result;
 }
 
 void AstNode::allocateDefaultEnumValues()
@@ -4824,4 +4871,54 @@ void AstNode::allocateDefaultEnumValues()
        }
 }
 
+bool AstNode::is_recursive_function() const
+{
+       std::set<const AstNode *> visited;
+       std::function<bool(const AstNode *node)> visit = [&](const AstNode *node) {
+               if (visited.count(node))
+                       return node == this;
+               visited.insert(node);
+               if (node->type == AST_FCALL) {
+                       auto it = current_scope.find(node->str);
+                       if (it != current_scope.end() && visit(it->second))
+                               return true;
+               }
+               for (const AstNode *child : node->children) {
+                       if (visit(child))
+                               return true;
+               }
+               return false;
+       };
+
+       log_assert(type == AST_FUNCTION);
+       return visit(this);
+}
+
+std::pair<AstNode*, AstNode*> AstNode::get_tern_choice()
+{
+       if (!children[0]->isConst())
+               return {};
+
+       bool found_sure_true = false;
+       bool found_maybe_true = false;
+
+       if (children[0]->type == AST_CONSTANT)
+               for (auto &bit : children[0]->bits) {
+                       if (bit == RTLIL::State::S1)
+                               found_sure_true = true;
+                       if (bit > RTLIL::State::S1)
+                               found_maybe_true = true;
+               }
+       else
+               found_sure_true = children[0]->asReal(true) != 0;
+
+       AstNode *choice = nullptr, *not_choice = nullptr;
+       if (found_sure_true)
+               choice = children[1], not_choice = children[2];
+       else if (!found_maybe_true)
+               choice = children[2], not_choice = children[1];
+
+       return {choice, not_choice};
+}
+
 YOSYS_NAMESPACE_END