diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h
index 907392166..6f173ca22 100644
--- a/frontends/ast/ast.h
+++ b/frontends/ast/ast.h
@@ -252,8 +252,8 @@ namespace AST
 		bool simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param);
 		void replace_result_wire_name_in_function(const std::string &from, const std::string &to);
 		AstNode *readmem(bool is_readmemh, std::string mem_filename, AstNode *memory, int start_addr, int finish_addr, bool unconditional_init);
-		void expand_genblock(std::string index_var, std::string prefix, std::map<std::string, std::string> &name_map, bool original_scope = true);
-		void replace_ids(const std::string &prefix, const std::map<std::string, std::string> &rules);
+		void expand_genblock(const std::string &prefix);
+		void label_genblks(std::set<std::string>& existing, int &counter);
 		void mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg_places,
 				dict<AstNode*, uint32_t> &mem2reg_flags, dict<AstNode*, uint32_t> &proc_flags, uint32_t &status_flags);
 		bool mem2reg_as_needed_pass2(pool<AstNode*> &mem2reg_set, AstNode *mod, AstNode *block, AstNode *&async_block);
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index fc2976c83..77911e966 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -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().
@@ -748,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];
@@ -1855,19 +1868,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 +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++)
@@ -1942,8 +1962,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 +1998,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 +2076,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,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))
@@ -3177,11 +3198,11 @@ 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);
+				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;
 			}
 
@@ -3192,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;
@@ -3203,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)) { }
 
@@ -3256,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;
@@ -3318,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;
@@ -3329,15 +3347,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())
 				{
@@ -3381,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());
 
@@ -3405,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 = "";
@@ -3859,63 +3865,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;
+			}
+
+			// 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;
 				}
 			}
+
 		}
 	}
 
-	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;
-
-		// 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 +3962,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;
+
+		child->expand_genblock(prefix);
 	}
-
-
-	if (backup_name_map.size() > 0)
-		name_map.swap(backup_name_map);
 }
 
-// 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;
+	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) {
-				new_rules[child->str] = new_prefix + child->str;
-				child->str = new_prefix + child->str;
-			}
-
-		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;
 	}
 }
 
@@ -4773,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();
diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y
index 8bd58d24c..6255a4204 100644
--- a/frontends/verilog/verilog_parser.y
+++ b/frontends/verilog/verilog_parser.y
@@ -770,6 +770,7 @@ module_body:
 	module_body module_body_stmt |
 	/* the following line makes the generate..endgenrate keywords optional */
 	module_body gen_stmt |
+	module_body gen_block |
 	module_body ';' |
 	%empty;
 
@@ -2459,6 +2460,16 @@ behavioral_stmt:
 		exitTypeScope();
 		if ($4 != NULL && $8 != NULL && *$4 != *$8)
 			frontend_verilog_yyerror("Begin label (%s) and end label (%s) don't match.", $4->c_str()+1, $8->c_str()+1);
+		AstNode *node = ast_stack.back();
+		// In SystemVerilog, unnamed blocks with block item declarations
+		// create an implicit hierarchy scope
+		if (sv_mode && node->str.empty())
+		    for (const AstNode* child : node->children)
+			if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER
+				|| child->type == AST_LOCALPARAM || child->type == AST_TYPEDEF) {
+			    node->str = "$unnamed_block$" + std::to_string(autoidx++);
+			    break;
+			}
 		SET_AST_NODE_LOC(ast_stack.back(), @2, @8);
 		delete $4;
 		delete $8;
@@ -2473,6 +2484,7 @@ behavioral_stmt:
 		ast_stack.back()->children.push_back($7);
 	} ';' simple_behavioral_stmt ')' {
 		AstNode *block = new AstNode(AST_BLOCK);
+		block->str = "$for_loop$" + std::to_string(autoidx++);
 		ast_stack.back()->children.push_back(block);
 		ast_stack.push_back(block);
 	} behavioral_stmt {
@@ -2722,6 +2734,7 @@ single_arg:
 
 module_gen_body:
 	module_gen_body gen_stmt_or_module_body_stmt |
+	module_gen_body gen_block |
 	%empty;
 
 gen_stmt_or_module_body_stmt:
@@ -2747,12 +2760,7 @@ gen_stmt:
 		ast_stack.back()->children.push_back(node);
 		ast_stack.push_back(node);
 		ast_stack.back()->children.push_back($3);
-		AstNode *block = new AstNode(AST_GENBLOCK);
-		ast_stack.back()->children.push_back(block);
-		ast_stack.push_back(block);
-	} gen_stmt_block {
-		ast_stack.pop_back();
-	} opt_gen_else {
+	} gen_stmt_block opt_gen_else {
 		SET_AST_NODE_LOC(ast_stack.back(), @1, @7);
 		ast_stack.pop_back();
 	} |
@@ -2765,20 +2773,6 @@ gen_stmt:
 		SET_AST_NODE_LOC(ast_stack.back(), @1, @7);
 		ast_stack.pop_back();
 	} |
-	TOK_BEGIN {
-		enterTypeScope();
-	} opt_label {
-		AstNode *node = new AstNode(AST_GENBLOCK);
-		node->str = $3 ? *$3 : std::string();
-		ast_stack.back()->children.push_back(node);
-		ast_stack.push_back(node);
-	} module_gen_body TOK_END opt_label {
-		exitTypeScope();
-		delete $3;
-		delete $7;
-		SET_AST_NODE_LOC(ast_stack.back(), @1, @7);
-		ast_stack.pop_back();
-	} |
 	TOK_MSG_TASKS {
 		AstNode *node = new AstNode(AST_TECALL);
 		node->str = *$1;
@@ -2790,6 +2784,23 @@ gen_stmt:
 		ast_stack.pop_back();
 	};
 
+gen_block:
+	TOK_BEGIN {
+		enterTypeScope();
+	} opt_label {
+		AstNode *node = new AstNode(AST_GENBLOCK);
+		node->str = $3 ? *$3 : std::string();
+		ast_stack.back()->children.push_back(node);
+		ast_stack.push_back(node);
+	} module_gen_body TOK_END opt_label {
+		exitTypeScope();
+		delete $3;
+		delete $7;
+		SET_AST_NODE_LOC(ast_stack.back(), @1, @7);
+		ast_stack.pop_back();
+	};
+
+// result is wrapped in a genblock only if necessary
 gen_stmt_block:
 	{
 		AstNode *node = new AstNode(AST_GENBLOCK);
@@ -2798,7 +2809,7 @@ gen_stmt_block:
 	} gen_stmt_or_module_body_stmt {
 		SET_AST_NODE_LOC(ast_stack.back(), @2, @2);
 		ast_stack.pop_back();
-	};
+	} | gen_block;
 
 opt_gen_else:
 	TOK_ELSE gen_stmt_block | %empty %prec FAKE_THEN;
diff --git a/kernel/rtlil.h b/kernel/rtlil.h
index cd966b815..4dad3c428 100644
--- a/kernel/rtlil.h
+++ b/kernel/rtlil.h
@@ -334,6 +334,10 @@ namespace RTLIL
 			return compare(size()-len, len, suffix) == 0;
 		}
 
+		bool contains(const char* str) const {
+			return strstr(c_str(), str);
+		}
+
 		size_t size() const {
 			return strlen(c_str());
 		}
diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc
index d43737c8d..96843d710 100644
--- a/passes/techmap/techmap.cc
+++ b/passes/techmap/techmap.cc
@@ -118,19 +118,14 @@ struct TechmapWorker
 			return result;
 
 		for (auto w : module->wires()) {
-			const char *p = w->name.c_str();
-			if (*p == '$')
+			if (*w->name.c_str() == '$')
 				continue;
 
-			const char *q = strrchr(p+1, '.');
-			if (q)
-				p = q;
-
-			if (!strncmp(p, "\\_TECHMAP_", 10)) {
+			if (w->name.contains("_TECHMAP_") && !w->name.contains("_TECHMAP_REPLACE_")) {
 				TechmapWireData record;
 				record.wire = w;
 				record.value = w;
-				result[p].push_back(record);
+				result[w->name].push_back(record);
 				w->set_bool_attribute(ID::keep);
 				w->set_bool_attribute(ID::_techmap_special_);
 			}
@@ -165,7 +160,7 @@ struct TechmapWorker
 
 		orig_cell_name = cell->name.str();
 		for (auto tpl_cell : tpl->cells())
-			if (tpl_cell->name == ID::_TECHMAP_REPLACE_) {
+			if (tpl_cell->name.ends_with("_TECHMAP_REPLACE_")) {
 				module->rename(cell, stringf("$techmap%d", autoidx++) + cell->name.str());
 				break;
 			}
@@ -226,8 +221,8 @@ struct TechmapWorker
 			}
 			design->select(module, w);
 
-			if (tpl_w->name.begins_with("\\_TECHMAP_REPLACE_.")) {
-				IdString replace_name = stringf("%s%s", orig_cell_name.c_str(), tpl_w->name.c_str() + strlen("\\_TECHMAP_REPLACE_"));
+			if (const char *p = strstr(tpl_w->name.c_str(), "_TECHMAP_REPLACE_.")) {
+				IdString replace_name = stringf("%s%s", orig_cell_name.c_str(), p + strlen("_TECHMAP_REPLACE_"));
 				Wire *replace_w = module->addWire(replace_name, tpl_w);
 				module->connect(replace_w, w);
 			}
@@ -327,12 +322,12 @@ struct TechmapWorker
 		for (auto tpl_cell : tpl->cells())
 		{
 			IdString c_name = tpl_cell->name;
-			bool techmap_replace_cell = (c_name == ID::_TECHMAP_REPLACE_);
+			bool techmap_replace_cell = c_name.ends_with("_TECHMAP_REPLACE_");
 
 			if (techmap_replace_cell)
 				c_name = orig_cell_name;
-			else if (tpl_cell->name.begins_with("\\_TECHMAP_REPLACE_."))
-				c_name = stringf("%s%s", orig_cell_name.c_str(), c_name.c_str() + strlen("\\_TECHMAP_REPLACE_"));
+			else if (const char *p = strstr(tpl_cell->name.c_str(), "_TECHMAP_REPLACE_."))
+				c_name = stringf("%s%s", orig_cell_name.c_str(), p + strlen("_TECHMAP_REPLACE_"));
 			else
 				apply_prefix(cell->name, c_name);
 
@@ -730,12 +725,16 @@ struct TechmapWorker
 						for (auto &it : twd)
 							techmap_wire_names.insert(it.first);
 
-						for (auto &it : twd[ID::_TECHMAP_FAIL_]) {
-							RTLIL::SigSpec value = it.value;
-							if (value.is_fully_const() && value.as_bool()) {
-								log("Not using module `%s' from techmap as it contains a %s marker wire with non-zero value %s.\n",
-										derived_name.c_str(), log_id(it.wire->name), log_signal(value));
-								techmap_do_cache[tpl] = false;
+						for (auto &it : twd) {
+							if (!it.first.ends_with("_TECHMAP_FAIL_"))
+								continue;
+							for (const TechmapWireData &elem : it.second) {
+								RTLIL::SigSpec value = elem.value;
+								if (value.is_fully_const() && value.as_bool()) {
+									log("Not using module `%s' from techmap as it contains a %s marker wire with non-zero value %s.\n",
+											derived_name.c_str(), log_id(elem.wire->name), log_signal(value));
+									techmap_do_cache[tpl] = false;
+								}
 							}
 						}
 
@@ -744,7 +743,7 @@ struct TechmapWorker
 
 						for (auto &it : twd)
 						{
-							if (!it.first.begins_with("\\_TECHMAP_DO_") || it.second.empty())
+							if (!it.first.contains("_TECHMAP_DO_") || it.second.empty())
 								continue;
 
 							auto &data = it.second.front();
@@ -756,7 +755,7 @@ struct TechmapWorker
 
 							const char *p = data.wire->name.c_str();
 							const char *q = strrchr(p+1, '.');
-							q = q ? q : p+1;
+							q = q ? q+1 : p+1;
 
 							std::string cmd_string = data.value.as_const().decode_string();
 
@@ -873,7 +872,7 @@ struct TechmapWorker
 
 					TechmapWires twd = techmap_find_special_wires(tpl);
 					for (auto &it : twd) {
-						if (it.first != ID::_TECHMAP_FAIL_ && (!it.first.begins_with("\\_TECHMAP_REMOVEINIT_") || !it.first.ends_with("_")) && !it.first.begins_with("\\_TECHMAP_DO_") && !it.first.begins_with("\\_TECHMAP_DONE_"))
+						if (!it.first.ends_with("_TECHMAP_FAIL_") && (!it.first.begins_with("\\_TECHMAP_REMOVEINIT_") || !it.first.ends_with("_")) && !it.first.contains("_TECHMAP_DO_") && !it.first.contains("_TECHMAP_DONE_"))
 							log_error("Techmap yielded unknown config wire %s.\n", log_id(it.first));
 						if (techmap_do_cache[tpl])
 							for (auto &it2 : it.second)
diff --git a/techlibs/common/cmp2lcu.v b/techlibs/common/cmp2lcu.v
index a221727e7..4e62039e9 100644
--- a/techlibs/common/cmp2lcu.v
+++ b/techlibs/common/cmp2lcu.v
@@ -41,10 +41,7 @@ generate
         wire [WIDTH-1:0] BB = {{(WIDTH-B_WIDTH){B_SIGNED ? B[B_WIDTH-1] : 1'b0}}, B};
         // For $ge operation, start with the assumption that A and B are
         //   equal (propagating this equality if A and B turn out to be so)
-        if (_TECHMAP_CELLTYPE_ == "$ge")
-            localparam CI = 1'b1;
-        else
-            localparam CI = 1'b0;
+        localparam CI = _TECHMAP_CELLTYPE_ == "$ge";
         $__CMP2LCU #(.AB_WIDTH(WIDTH), .AB_SIGNED(A_SIGNED && B_SIGNED), .LCU_WIDTH(1), .BUDGET(`LUT_WIDTH), .CI(CI))
             _TECHMAP_REPLACE_ (.A(AA), .B(BB), .P(1'b1), .G(1'b0), .Y(Y));
     end
@@ -81,12 +78,12 @@ generate
         assign Y = CO[LCU_WIDTH-1];
     end
     else begin
-        if (_TECHMAP_CONSTMSK_A_[AB_WIDTH-1:0] && _TECHMAP_CONSTMSK_B_[AB_WIDTH-1:0])
-            localparam COST = 0;
-        else if (_TECHMAP_CONSTMSK_A_[AB_WIDTH-1:0] || _TECHMAP_CONSTMSK_B_[AB_WIDTH-1:0])
-            localparam COST = 1;
-        else
-            localparam COST = 2;
+        localparam COST =
+            _TECHMAP_CONSTMSK_A_[AB_WIDTH-1:0] && _TECHMAP_CONSTMSK_B_[AB_WIDTH-1:0]
+            ? 0
+            : (_TECHMAP_CONSTMSK_A_[AB_WIDTH-1:0] || _TECHMAP_CONSTMSK_B_[AB_WIDTH-1:0]
+                ? 1
+                : 2);
 
         if (BUDGET < COST)
              $__CMP2LCU #(.AB_WIDTH(AB_WIDTH), .AB_SIGNED(AB_SIGNED), .LCU_WIDTH(LCU_WIDTH+1), .BUDGET(`LUT_WIDTH), .CI(CI))
@@ -104,21 +101,21 @@ generate
                 //   from MSB down, deferring to less significant bits if the
                 //   MSBs are equal
                 assign GG = P[0] & (A[AB_WIDTH-1] & ~B[AB_WIDTH-1]);
+            (* force_downto *)
+            wire [LCU_WIDTH-1:0] P_, G_;
             if (LCU_WIDTH == 1) begin
                 // Propagate only if all pairs are equal
                 //   (inconclusive evidence to say A >= B)
-                wire P_ = P[0] & PP;
+                assign P_ = P[0] & PP;
                 // Generate if any comparisons call for it
-                wire G_ = G[0] | GG;
+                assign G_ = G[0] | GG;
             end
             else begin
                 // Propagate only if all pairs are equal
                 //   (inconclusive evidence to say A >= B)
-                (* force_downto *)
-                wire [LCU_WIDTH-1:0] P_ = {P[LCU_WIDTH-1:1], P[0] & PP};
+                assign P_ = {P[LCU_WIDTH-1:1], P[0] & PP};
                 // Generate if any comparisons call for it
-                (* force_downto *)
-                wire [LCU_WIDTH-1:0] G_ = {G[LCU_WIDTH-1:1], G[0] | GG};
+                assign G_ = {G[LCU_WIDTH-1:1], G[0] | GG};
             end
             if (AB_WIDTH == 1)
                $__CMP2LCU #(.AB_WIDTH(AB_WIDTH-1), .AB_SIGNED(1'b0), .LCU_WIDTH(LCU_WIDTH), .BUDGET(BUDGET-COST), .CI(CI))
diff --git a/techlibs/common/cmp2lut.v b/techlibs/common/cmp2lut.v
index ec8f98e8d..c753bd2f1 100644
--- a/techlibs/common/cmp2lut.v
+++ b/techlibs/common/cmp2lut.v
@@ -66,14 +66,12 @@ function automatic [(1 << `LUT_WIDTH)-1:0] gen_lut;
 endfunction
 
 generate
-	if (_TECHMAP_CELLTYPE_ == "$lt")
-		localparam operation = 0;
-	if (_TECHMAP_CELLTYPE_ == "$le")
-		localparam operation = 1;
-	if (_TECHMAP_CELLTYPE_ == "$gt")
-		localparam operation = 2;
-	if (_TECHMAP_CELLTYPE_ == "$ge")
-		localparam operation = 3;
+	localparam operation =
+		_TECHMAP_CELLTYPE_ == "$lt" ? 0 :
+		_TECHMAP_CELLTYPE_ == "$le" ? 1 :
+		_TECHMAP_CELLTYPE_ == "$gt" ? 2 :
+		_TECHMAP_CELLTYPE_ == "$ge" ? 3 :
+		-1;
 
 	if (A_WIDTH > `LUT_WIDTH || B_WIDTH > `LUT_WIDTH || Y_WIDTH != 1)
 		wire _TECHMAP_FAIL_ = 1;
diff --git a/techlibs/common/mul2dsp.v b/techlibs/common/mul2dsp.v
index bec47d01f..f22f47b4a 100644
--- a/techlibs/common/mul2dsp.v
+++ b/techlibs/common/mul2dsp.v
@@ -121,7 +121,7 @@ module _80_mul (A, B, Y);
 			localparam partial_Y_WIDTH = `MIN(Y_WIDTH, B_WIDTH+`DSP_A_MAXWIDTH_PARTIAL);
 			localparam last_A_WIDTH = A_WIDTH-n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom);
 			localparam last_Y_WIDTH = B_WIDTH+last_A_WIDTH;
-			if (A_SIGNED && B_SIGNED) begin
+			if (A_SIGNED && B_SIGNED) begin : blk
 				(* force_downto *)
 				wire signed [partial_Y_WIDTH-1:0] partial [n-1:0];
 				(* force_downto *)
@@ -129,7 +129,7 @@ module _80_mul (A, B, Y);
 				(* force_downto *)
 				wire signed [Y_WIDTH-1:0] partial_sum [n:0];
 			end
-			else begin
+			else begin : blk
 				(* force_downto *)
 				wire [partial_Y_WIDTH-1:0] partial [n-1:0];
 				(* force_downto *)
@@ -148,15 +148,15 @@ module _80_mul (A, B, Y);
 				) mul (
 					.A({{sign_headroom{1'b0}}, A[i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_A_MAXWIDTH_PARTIAL-sign_headroom]}),
 					.B(B),
-					.Y(partial[i])
+					.Y(blk.partial[i])
 				);
 				// TODO: Currently a 'cascade' approach to summing the partial
 				//       products is taken here, but a more efficient 'binary
 				//       reduction' approach also exists...
 				if (i == 0)
-					assign partial_sum[i] = partial[i];
+					assign blk.partial_sum[i] = blk.partial[i];
 				else
-					assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1];
+					assign blk.partial_sum[i] = (blk.partial[i] << (* mul2dsp *) i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) blk.partial_sum[i-1];
 			end
 
 			\$__mul #(
@@ -168,17 +168,17 @@ module _80_mul (A, B, Y);
 			) sliceA.last (
 				.A(A[A_WIDTH-1 -: last_A_WIDTH]),
 				.B(B),
-				.Y(last_partial)
+				.Y(blk.last_partial)
 			);
-			assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1];
-			assign Y = partial_sum[n];
+			assign blk.partial_sum[n] = (blk.last_partial << (* mul2dsp *) n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) blk.partial_sum[n-1];
+			assign Y = blk.partial_sum[n];
 		end
 		else if (B_WIDTH > `DSP_B_MAXWIDTH) begin
 			localparam n = (B_WIDTH-`DSP_B_MAXWIDTH+`DSP_B_MAXWIDTH_PARTIAL-sign_headroom-1) / (`DSP_B_MAXWIDTH_PARTIAL-sign_headroom);
 			localparam partial_Y_WIDTH = `MIN(Y_WIDTH, A_WIDTH+`DSP_B_MAXWIDTH_PARTIAL);
 			localparam last_B_WIDTH = B_WIDTH-n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom);
 			localparam last_Y_WIDTH = A_WIDTH+last_B_WIDTH;
-			if (A_SIGNED && B_SIGNED) begin
+			if (A_SIGNED && B_SIGNED) begin : blk
 				(* force_downto *)
 				wire signed [partial_Y_WIDTH-1:0] partial [n-1:0];
 				(* force_downto *)
@@ -186,7 +186,7 @@ module _80_mul (A, B, Y);
 				(* force_downto *)
 				wire signed [Y_WIDTH-1:0] partial_sum [n:0];
 			end
-			else begin
+			else begin : blk
 				(* force_downto *)
 				wire [partial_Y_WIDTH-1:0] partial [n-1:0];
 				(* force_downto *)
@@ -205,15 +205,15 @@ module _80_mul (A, B, Y);
 				) mul (
 					.A(A),
 					.B({{sign_headroom{1'b0}}, B[i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_B_MAXWIDTH_PARTIAL-sign_headroom]}),
-					.Y(partial[i])
+					.Y(blk.partial[i])
 				);
 				// TODO: Currently a 'cascade' approach to summing the partial
 				//       products is taken here, but a more efficient 'binary
 				//       reduction' approach also exists...
 				if (i == 0)
-					assign partial_sum[i] = partial[i];
+					assign blk.partial_sum[i] = blk.partial[i];
 				else
-					assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1];
+					assign blk.partial_sum[i] = (blk.partial[i] << (* mul2dsp *) i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) blk.partial_sum[i-1];
 			end
 
 			\$__mul #(
@@ -225,20 +225,24 @@ module _80_mul (A, B, Y);
 			) mul_sliceB_last (
 				.A(A),
 				.B(B[B_WIDTH-1 -: last_B_WIDTH]),
-				.Y(last_partial)
+				.Y(blk.last_partial)
 			);
-			assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1];
-			assign Y = partial_sum[n];
+			assign blk.partial_sum[n] = (blk.last_partial << (* mul2dsp *) n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) blk.partial_sum[n-1];
+			assign Y = blk.partial_sum[n];
 		end
 		else begin
-			if (A_SIGNED)
+			if (A_SIGNED) begin : blkA
 				wire signed [`DSP_A_MAXWIDTH-1:0] Aext = $signed(A);
-			else
+			end
+			else begin : blkA
 				wire [`DSP_A_MAXWIDTH-1:0] Aext = A;
-			if (B_SIGNED)
+			end
+			if (B_SIGNED) begin : blkB
 				wire signed [`DSP_B_MAXWIDTH-1:0] Bext = $signed(B);
-			else
+			end
+			else begin : blkB
 				wire [`DSP_B_MAXWIDTH-1:0] Bext = B;
+			end
 
 			`DSP_NAME #(
 				.A_SIGNED(A_SIGNED),
@@ -247,8 +251,8 @@ module _80_mul (A, B, Y);
 				.B_WIDTH(`DSP_B_MAXWIDTH),
 				.Y_WIDTH(`MIN(Y_WIDTH,`DSP_A_MAXWIDTH+`DSP_B_MAXWIDTH)),
 			) _TECHMAP_REPLACE_ (
-				.A(Aext),
-				.B(Bext),
+				.A(blkA.Aext),
+				.B(blkB.Bext),
 				.Y(Y)
 			);
 		end
diff --git a/techlibs/ice40/brams_map.v b/techlibs/ice40/brams_map.v
index ad3bccd21..db9f5d8ce 100644
--- a/techlibs/ice40/brams_map.v
+++ b/techlibs/ice40/brams_map.v
@@ -254,6 +254,41 @@ module \$__ICE40_RAM4K_M123 (CLK2, CLK3, A1ADDR, A1DATA, A1EN, B1ADDR, B1DATA, B
 
 	wire [15:0] A1DATA_16, B1DATA_16;
 
+`define INSTANCE \
+	\$__ICE40_RAM4K #( \
+		.READ_MODE(MODE), \
+		.WRITE_MODE(MODE), \
+		.NEGCLK_R(!CLKPOL2), \
+		.NEGCLK_W(!CLKPOL3), \
+		.INIT_0(INIT_0), \
+		.INIT_1(INIT_1), \
+		.INIT_2(INIT_2), \
+		.INIT_3(INIT_3), \
+		.INIT_4(INIT_4), \
+		.INIT_5(INIT_5), \
+		.INIT_6(INIT_6), \
+		.INIT_7(INIT_7), \
+		.INIT_8(INIT_8), \
+		.INIT_9(INIT_9), \
+		.INIT_A(INIT_A), \
+		.INIT_B(INIT_B), \
+		.INIT_C(INIT_C), \
+		.INIT_D(INIT_D), \
+		.INIT_E(INIT_E), \
+		.INIT_F(INIT_F) \
+	) _TECHMAP_REPLACE_ ( \
+		.RDATA(A1DATA_16), \
+		.RADDR(A1ADDR_11), \
+		.RCLK(CLK2), \
+		.RCLKE(A1EN), \
+		.RE(1'b1), \
+		.WDATA(B1DATA_16), \
+		.WADDR(B1ADDR_11), \
+		.WCLK(CLK3), \
+		.WCLKE(|B1EN), \
+		.WE(1'b1) \
+	);
+
 	generate
 		if (MODE == 1) begin
 			assign A1DATA = {A1DATA_16[14], A1DATA_16[12], A1DATA_16[10], A1DATA_16[ 8],
@@ -261,51 +296,23 @@ module \$__ICE40_RAM4K_M123 (CLK2, CLK3, A1ADDR, A1DATA, A1EN, B1ADDR, B1DATA, B
 			assign {B1DATA_16[14], B1DATA_16[12], B1DATA_16[10], B1DATA_16[ 8],
 			        B1DATA_16[ 6], B1DATA_16[ 4], B1DATA_16[ 2], B1DATA_16[ 0]} = B1DATA;
 			`include "brams_init1.vh"
+			`INSTANCE
 		end
 		if (MODE == 2) begin
 			assign A1DATA = {A1DATA_16[13], A1DATA_16[9], A1DATA_16[5], A1DATA_16[1]};
 			assign {B1DATA_16[13], B1DATA_16[9], B1DATA_16[5], B1DATA_16[1]} = B1DATA;
 			`include "brams_init2.vh"
+			`INSTANCE
 		end
 		if (MODE == 3) begin
 			assign A1DATA = {A1DATA_16[11], A1DATA_16[3]};
 			assign {B1DATA_16[11], B1DATA_16[3]} = B1DATA;
 			`include "brams_init3.vh"
+			`INSTANCE
 		end
 	endgenerate
 
-	\$__ICE40_RAM4K #(
-		.READ_MODE(MODE),
-		.WRITE_MODE(MODE),
-		.NEGCLK_R(!CLKPOL2),
-		.NEGCLK_W(!CLKPOL3),
-		.INIT_0(INIT_0),
-		.INIT_1(INIT_1),
-		.INIT_2(INIT_2),
-		.INIT_3(INIT_3),
-		.INIT_4(INIT_4),
-		.INIT_5(INIT_5),
-		.INIT_6(INIT_6),
-		.INIT_7(INIT_7),
-		.INIT_8(INIT_8),
-		.INIT_9(INIT_9),
-		.INIT_A(INIT_A),
-		.INIT_B(INIT_B),
-		.INIT_C(INIT_C),
-		.INIT_D(INIT_D),
-		.INIT_E(INIT_E),
-		.INIT_F(INIT_F)
-	) _TECHMAP_REPLACE_ (
-		.RDATA(A1DATA_16),
-		.RADDR(A1ADDR_11),
-		.RCLK(CLK2),
-		.RCLKE(A1EN),
-		.RE(1'b1),
-		.WDATA(B1DATA_16),
-		.WADDR(B1ADDR_11),
-		.WCLK(CLK3),
-		.WCLKE(|B1EN),
-		.WE(1'b1)
-	);
+`undef INSTANCE
+
 endmodule
 
diff --git a/techlibs/xilinx/arith_map.v b/techlibs/xilinx/arith_map.v
index eb8a04bde..63be7563e 100644
--- a/techlibs/xilinx/arith_map.v
+++ b/techlibs/xilinx/arith_map.v
@@ -151,6 +151,8 @@ generate if (`LUT_SIZE == 4) begin
 		);
 	end endgenerate
 
+	assign X = S;
+
 end else begin
 
 	localparam CARRY4_COUNT = (Y_WIDTH + 3) / 4;
@@ -193,8 +195,8 @@ end else begin
 		end
 	end endgenerate
 
-end endgenerate
-
 	assign X = S;
+
+end endgenerate
 endmodule
 
diff --git a/tests/simple/func_block.v b/tests/simple/func_block.v
new file mode 100644
index 000000000..be759d1a9
--- /dev/null
+++ b/tests/simple/func_block.v
@@ -0,0 +1,33 @@
+`default_nettype none
+
+module top(inp, out1, out2, out3);
+	input wire [31:0] inp;
+
+	function automatic [31:0] func1;
+		input [31:0] inp;
+		reg [31:0] idx;
+		for (idx = 0; idx < 32; idx = idx + 1) begin : blk
+			func1[idx] = (idx & 1'b1) ^ inp[idx];
+		end
+	endfunction
+
+	function automatic [31:0] func2;
+		input [31:0] inp;
+		reg [31:0] idx;
+		for (idx = 0; idx < 32; idx = idx + 1) begin : blk
+			func2[idx] = (idx & 1'b1) ^ inp[idx];
+		end
+	endfunction
+
+	function automatic [31:0] func3;
+		localparam A = 32 - 1;
+		parameter B = 1 - 0;
+		input [31:0] inp;
+		func3[A:B] = inp[A:B];
+	endfunction
+
+	output wire [31:0] out1, out2, out3;
+	assign out1 = func1(inp);
+	assign out2 = func2(inp);
+	assign out3 = func3(inp);
+endmodule
diff --git a/tests/simple/func_recurse.v b/tests/simple/func_recurse.v
new file mode 100644
index 000000000..d61c8cc06
--- /dev/null
+++ b/tests/simple/func_recurse.v
@@ -0,0 +1,25 @@
+module top(
+	input wire [3:0] inp,
+	output wire [3:0] out1, out2
+);
+	function automatic [3:0] pow_a;
+		input [3:0] base, exp;
+		begin
+			pow_a = 1;
+			if (exp > 0)
+				pow_a = base * pow_a(base, exp - 1);
+		end
+	endfunction
+
+	function automatic [3:0] pow_b;
+		input [3:0] base, exp;
+		begin
+			pow_b = 1;
+			if (exp > 0)
+				pow_b = base * pow_b(base, exp - 1);
+		end
+	endfunction
+
+	assign out1 = pow_a(inp, 3);
+	assign out2 = pow_b(2, 2);
+endmodule
diff --git a/tests/simple/func_width_scope.v b/tests/simple/func_width_scope.v
new file mode 100644
index 000000000..ce81e894e
--- /dev/null
+++ b/tests/simple/func_width_scope.v
@@ -0,0 +1,41 @@
+module top(inp, out1, out2);
+	input wire signed inp;
+
+	localparam WIDTH_A = 5;
+	function automatic [WIDTH_A-1:0] func1;
+		input reg [WIDTH_A-1:0] inp;
+		func1 = ~inp;
+	endfunction
+	wire [func1(0)-1:0] xc;
+	assign xc = 1'sb1;
+	wire [WIDTH_A-1:0] xn;
+	assign xn = func1(inp);
+
+	generate
+		if (1) begin : blk
+			localparam WIDTH_A = 6;
+			function automatic [WIDTH_A-1:0] func2;
+				input reg [WIDTH_A-1:0] inp;
+				func2 = ~inp;
+			endfunction
+			wire [func2(0)-1:0] yc;
+			assign yc = 1'sb1;
+			wire [WIDTH_A-1:0] yn;
+			assign yn = func2(inp);
+
+			localparam WIDTH_B = 7;
+			function automatic [WIDTH_B-1:0] func3;
+				input reg [WIDTH_B-1:0] inp;
+				func3 = ~inp;
+			endfunction
+			wire [func3(0)-1:0] zc;
+			assign zc = 1'sb1;
+			wire [WIDTH_B-1:0] zn;
+			assign zn = func3(inp);
+		end
+	endgenerate
+
+	output wire [1023:0] out1, out2;
+	assign out1 = {xc, 1'b0, blk.yc, 1'b0, blk.zc};
+	assign out2 = {xn, 1'b0, blk.yn, 1'b0, blk.zn};
+endmodule
diff --git a/tests/simple/genblk_collide.v b/tests/simple/genblk_collide.v
new file mode 100644
index 000000000..f42dd2cfc
--- /dev/null
+++ b/tests/simple/genblk_collide.v
@@ -0,0 +1,27 @@
+`default_nettype none
+
+module top1;
+	generate
+		if (1) begin : foo
+			if (1) begin : bar
+				wire x;
+			end
+			assign bar.x = 1;
+			wire y;
+		end
+	endgenerate
+endmodule
+
+module top2;
+	genvar i;
+	generate
+		if (1) begin : foo
+			wire x;
+			for (i = 0; i < 1; i = i + 1) begin : foo
+				if (1) begin : foo
+					assign x = 1;
+				end
+			end
+		end
+	endgenerate
+endmodule
diff --git a/tests/simple/genblk_dive.v b/tests/simple/genblk_dive.v
new file mode 100644
index 000000000..98d0e1f4b
--- /dev/null
+++ b/tests/simple/genblk_dive.v
@@ -0,0 +1,21 @@
+`default_nettype none
+module top(output wire x);
+	generate
+		if (1) begin : Z
+			if (1) begin : A
+				wire x;
+				if (1) begin : B
+					wire x;
+					if (1) begin : C
+						wire x;
+						assign B.x = 0;
+						wire z = A.B.C.x;
+					end
+					assign A.x = A.B.C.x;
+				end
+				assign B.C.x = B.x;
+			end
+		end
+	endgenerate
+	assign x = Z.A.x;
+endmodule
diff --git a/tests/simple/genblk_order.v b/tests/simple/genblk_order.v
new file mode 100644
index 000000000..7c3a7a756
--- /dev/null
+++ b/tests/simple/genblk_order.v
@@ -0,0 +1,18 @@
+`default_nettype none
+module top(
+	output wire out1,
+	output wire out2
+);
+	generate
+		if (1) begin : outer
+			if (1) begin : foo
+				wire x = 0;
+				if (1) begin : foo
+					wire x = 1;
+					assign out1 = foo.x;
+				end
+				assign out2 = foo.x;
+			end
+		end
+	endgenerate
+endmodule
diff --git a/tests/simple/generate.v b/tests/simple/generate.v
index 12327b36e..ac4dd81a8 100644
--- a/tests/simple/generate.v
+++ b/tests/simple/generate.v
@@ -260,3 +260,66 @@ module gen_test8;
 	`ASSERT(gen_test8.A.C.x == 1)
 	`ASSERT(gen_test8.A.B.x == 0)
 endmodule
+
+// ------------------------------------------
+
+module gen_test9;
+
+// `define VERIFY
+`ifdef VERIFY
+	`define ASSERT(expr) assert property (expr);
+`else
+	`define ASSERT(expr)
+`endif
+
+	wire [1:0] w = 2'b11;
+	generate
+		begin : A
+			wire [1:0] x;
+			begin : B
+				wire [1:0] y = 2'b00;
+				`ASSERT(w == 3)
+				`ASSERT(x == 2)
+				`ASSERT(y == 0)
+				`ASSERT(A.x == 2)
+				`ASSERT(A.C.z == 1)
+				`ASSERT(A.B.y == 0)
+				`ASSERT(gen_test9.w == 3)
+				`ASSERT(gen_test9.A.x == 2)
+				`ASSERT(gen_test9.A.C.z == 1)
+				`ASSERT(gen_test9.A.B.y == 0)
+			end
+			begin : C
+				wire [1:0] z = 2'b01;
+				`ASSERT(w == 3)
+				`ASSERT(x == 2)
+				`ASSERT(z == 1)
+				`ASSERT(A.x == 2)
+				`ASSERT(A.C.z == 1)
+				`ASSERT(A.B.y == 0)
+				`ASSERT(gen_test9.w == 3)
+				`ASSERT(gen_test9.A.x == 2)
+				`ASSERT(gen_test9.A.C.z == 1)
+				`ASSERT(gen_test9.A.B.y == 0)
+			end
+			assign x = B.y ^ 2'b11 ^ C.z;
+			`ASSERT(x == 2)
+			`ASSERT(A.x == 2)
+			`ASSERT(A.C.z == 1)
+			`ASSERT(A.B.y == 0)
+			`ASSERT(gen_test9.w == 3)
+			`ASSERT(gen_test9.A.x == 2)
+			`ASSERT(gen_test9.A.C.z == 1)
+			`ASSERT(gen_test9.A.B.y == 0)
+		end
+	endgenerate
+
+	`ASSERT(w == 3)
+	`ASSERT(A.x == 2)
+	`ASSERT(A.C.z == 1)
+	`ASSERT(A.B.y == 0)
+	`ASSERT(gen_test9.w == 3)
+	`ASSERT(gen_test9.A.x == 2)
+	`ASSERT(gen_test9.A.C.z == 1)
+	`ASSERT(gen_test9.A.B.y == 0)
+endmodule
diff --git a/tests/simple/local_loop_var.sv b/tests/simple/local_loop_var.sv
new file mode 100644
index 000000000..46b4e5c22
--- /dev/null
+++ b/tests/simple/local_loop_var.sv
@@ -0,0 +1,11 @@
+module top(out);
+	output integer out;
+	initial begin
+		integer i;
+		for (i = 0; i < 5; i = i + 1)
+			if (i == 0)
+				out = 1;
+			else
+				out += 2 ** i;
+	end
+endmodule
diff --git a/tests/simple/loop_var_shadow.v b/tests/simple/loop_var_shadow.v
new file mode 100644
index 000000000..0222a4493
--- /dev/null
+++ b/tests/simple/loop_var_shadow.v
@@ -0,0 +1,15 @@
+module top(out);
+	genvar i;
+	generate
+		for (i = 0; i < 2; i = i + 1) begin : loop
+			localparam j = i + 1;
+			if (1) begin : blk
+				localparam i = j + 1;
+				wire [i:0] x;
+				assign x = 1'sb1;
+			end
+		end
+	endgenerate
+	output wire [63:0] out;
+	assign out = {loop[0].blk.x, loop[1].blk.x};
+endmodule
diff --git a/tests/simple/named_genblk.v b/tests/simple/named_genblk.v
new file mode 100644
index 000000000..b8300fc4d
--- /dev/null
+++ b/tests/simple/named_genblk.v
@@ -0,0 +1,27 @@
+`default_nettype none
+module top;
+	generate
+		if (1) begin
+			wire t;
+			begin : foo
+				wire x;
+			end
+			wire u;
+		end
+		begin : bar
+			wire x;
+			wire y;
+			begin : baz
+				wire x;
+				wire z;
+			end
+		end
+	endgenerate
+	assign genblk1.t = 1;
+	assign genblk1.foo.x = 1;
+	assign genblk1.u = 1;
+	assign bar.x = 1;
+	assign bar.y = 1;
+	assign bar.baz.x = 1;
+	assign bar.baz.z = 1;
+endmodule
diff --git a/tests/simple/nested_genblk_resolve.v b/tests/simple/nested_genblk_resolve.v
new file mode 100644
index 000000000..da5593f8a
--- /dev/null
+++ b/tests/simple/nested_genblk_resolve.v
@@ -0,0 +1,14 @@
+`default_nettype none
+module top;
+    generate
+        if (1) begin
+            wire x;
+            genvar i;
+            for (i = 0; i < 1; i = i + 1) begin
+                if (1) begin
+                    assign x = 1;
+                end
+            end
+        end
+    endgenerate
+endmodule
diff --git a/tests/simple/unnamed_block_decl.sv b/tests/simple/unnamed_block_decl.sv
new file mode 100644
index 000000000..e81b457a8
--- /dev/null
+++ b/tests/simple/unnamed_block_decl.sv
@@ -0,0 +1,17 @@
+module top(z);
+	output integer z;
+	initial begin
+		integer x;
+		x = 1;
+		begin
+			integer y;
+			y = x + 1;
+			begin
+				integer z;
+				z = y + 1;
+				y = z + 1;
+			end
+			z = y + 1;
+		end
+	end
+endmodule
diff --git a/tests/various/gen_if_null.v b/tests/various/gen_if_null.v
index a12ac6288..992bc68b3 100644
--- a/tests/various/gen_if_null.v
+++ b/tests/various/gen_if_null.v
@@ -1,13 +1,17 @@
-module test(x, y, z);
+`default_nettype none
+module test;
 	localparam OFF = 0;
 	generate
 		if (OFF) ;
-		else input x;
-		if (!OFF) input y;
+		else wire x;
+		if (!OFF) wire y;
 		else ;
 		if (OFF) ;
 		else ;
 		if (OFF) ;
-		input z;
+		wire z;
 	endgenerate
+	assign genblk1.x = 0;
+	assign genblk2.y = 0;
+	assign z = 0;
 endmodule
diff --git a/tests/various/gen_if_null.ys b/tests/various/gen_if_null.ys
index 31dfc444b..0733e3a94 100644
--- a/tests/various/gen_if_null.ys
+++ b/tests/various/gen_if_null.ys
@@ -1,4 +1,4 @@
 read_verilog gen_if_null.v
-select -assert-count 1 test/x
-select -assert-count 1 test/y
+select -assert-count 1 test/genblk1.x
+select -assert-count 1 test/genblk2.y
 select -assert-count 1 test/z
diff --git a/tests/verilog/bug2493.ys b/tests/verilog/bug2493.ys
new file mode 100644
index 000000000..380d2a823
--- /dev/null
+++ b/tests/verilog/bug2493.ys
@@ -0,0 +1,12 @@
+logger -expect error "Failed to detect width for identifier \\genblk1\.y!" 1
+read_verilog <<EOT
+module top1;
+    wire x;
+    generate
+        if (1) begin
+            mod y();
+            assign x = y;
+        end
+    endgenerate
+endmodule
+EOT
diff --git a/tests/verilog/bug656.v b/tests/verilog/bug656.v
new file mode 100644
index 000000000..068d045fd
--- /dev/null
+++ b/tests/verilog/bug656.v
@@ -0,0 +1,21 @@
+module top #(
+	parameter WIDTH = 6
+) (
+	input [WIDTH-1:0] a_i,
+	input [WIDTH-1:0] b_i,
+	output [WIDTH-1:0] z_o
+);
+	genvar g;
+	generate
+		for (g = 0; g < WIDTH; g = g + 1) begin
+			if (g > 2) begin
+				wire tmp;
+				assign tmp = a_i[g] || b_i[g];
+				assign z_o[g] = tmp;
+			end
+			else begin
+				assign z_o[g] = a_i[g] && b_i[g];
+			end
+		end
+	endgenerate
+endmodule
diff --git a/tests/verilog/bug656.ys b/tests/verilog/bug656.ys
new file mode 100644
index 000000000..7f367341a
--- /dev/null
+++ b/tests/verilog/bug656.ys
@@ -0,0 +1,13 @@
+read_verilog bug656.v
+
+select -assert-count 1 top/a_i
+select -assert-count 1 top/b_i
+select -assert-count 1 top/z_o
+
+select -assert-none top/genblk1[0].genblk1.tmp
+select -assert-none top/genblk1[1].genblk1.tmp
+select -assert-none top/genblk1[2].genblk1.tmp
+
+select -assert-count 1 top/genblk1[3].genblk1.tmp
+select -assert-count 1 top/genblk1[4].genblk1.tmp
+select -assert-count 1 top/genblk1[5].genblk1.tmp
diff --git a/tests/verilog/genblk_case.v b/tests/verilog/genblk_case.v
new file mode 100644
index 000000000..081fb09d3
--- /dev/null
+++ b/tests/verilog/genblk_case.v
@@ -0,0 +1,26 @@
+module top;
+	parameter YES = 1;
+	generate
+		if (YES) wire y;
+		else wire n;
+
+		if (!YES) wire n;
+		else wire y;
+
+		case (YES)
+			1: wire y;
+			0: wire n;
+		endcase
+
+		case (!YES)
+			0: wire y;
+			1: wire n;
+		endcase
+
+		if (YES) wire y;
+		else wire n;
+
+		if (!YES) wire n;
+		else wire y;
+	endgenerate
+endmodule
diff --git a/tests/verilog/genblk_case.ys b/tests/verilog/genblk_case.ys
new file mode 100644
index 000000000..3c1bb51f9
--- /dev/null
+++ b/tests/verilog/genblk_case.ys
@@ -0,0 +1,15 @@
+read_verilog genblk_case.v
+
+select -assert-count 0 top/genblk1.n
+select -assert-count 0 top/genblk2.n
+select -assert-count 0 top/genblk3.n
+select -assert-count 0 top/genblk4.n
+select -assert-count 0 top/genblk5.n
+select -assert-count 0 top/genblk6.n
+
+select -assert-count 1 top/genblk1.y
+select -assert-count 1 top/genblk2.y
+select -assert-count 1 top/genblk3.y
+select -assert-count 1 top/genblk4.y
+select -assert-count 1 top/genblk5.y
+select -assert-count 1 top/genblk6.y
diff --git a/tests/verilog/hidden_decl.ys b/tests/verilog/hidden_decl.ys
new file mode 100644
index 000000000..aed7847dc
--- /dev/null
+++ b/tests/verilog/hidden_decl.ys
@@ -0,0 +1,11 @@
+logger -expect error "Identifier `\\y' is implicitly declared and `default_nettype is set to none" 1
+read_verilog <<EOT
+`default_nettype none
+module top1;
+    wire x;
+    generate
+        if (1) wire y;
+    endgenerate
+    assign x = y;
+endmodule
+EOT
diff --git a/tests/verilog/unnamed_block.ys b/tests/verilog/unnamed_block.ys
new file mode 100644
index 000000000..0f209a089
--- /dev/null
+++ b/tests/verilog/unnamed_block.ys
@@ -0,0 +1,28 @@
+read_verilog <<EOT
+module top;
+    initial begin : blk
+        integer x;
+    end
+endmodule
+EOT
+
+delete
+
+read_verilog -sv <<EOT
+module top;
+    initial begin
+        integer x;
+    end
+endmodule
+EOT
+
+delete
+
+logger -expect error "Local declaration in unnamed block is only supported in SystemVerilog mode!" 1
+read_verilog <<EOT
+module top;
+    initial begin
+        integer x;
+    end
+endmodule
+EOT
diff --git a/tests/verilog/unnamed_genblk.sv b/tests/verilog/unnamed_genblk.sv
new file mode 100644
index 000000000..41828b1b0
--- /dev/null
+++ b/tests/verilog/unnamed_genblk.sv
@@ -0,0 +1,39 @@
+// This test is taken directly from Section 27.6 of IEEE 1800-2017
+
+module top;
+	parameter genblk2 = 0;
+	genvar i;
+
+	// The following generate block is implicitly named genblk1
+
+	if (genblk2) logic a; // top.genblk1.a
+	else logic b; // top.genblk1.b
+
+	// The following generate block is implicitly named genblk02
+	// as genblk2 is already a declared identifier
+
+	if (genblk2) logic a; // top.genblk02.a
+	else logic b; // top.genblk02.b
+
+	// The following generate block would have been named genblk3
+	// but is explicitly named g1
+
+	for (i = 0; i < 1; i = i + 1) begin : g1 // block name
+		// The following generate block is implicitly named genblk1
+		// as the first nested scope inside g1
+		if (1) logic a; // top.g1[0].genblk1.a
+	end
+
+	// The following generate block is implicitly named genblk4 since
+	// it belongs to the fourth generate construct in scope "top".
+	// The previous generate block would have been
+	// named genblk3 if it had not been explicitly named g1
+
+	for (i = 0; i < 1; i = i + 1)
+		// The following generate block is implicitly named genblk1
+		// as the first nested generate block in genblk4
+		if (1) logic a; // top.genblk4[0].genblk1.a
+
+	// The following generate block is implicitly named genblk5
+	if (1) logic a; // top.genblk5.a
+endmodule
diff --git a/tests/verilog/unnamed_genblk.ys b/tests/verilog/unnamed_genblk.ys
new file mode 100644
index 000000000..2b9aa9d69
--- /dev/null
+++ b/tests/verilog/unnamed_genblk.ys
@@ -0,0 +1,8 @@
+read_verilog -sv unnamed_genblk.sv
+select -assert-count 0 top/genblk1.a
+select -assert-count 1 top/genblk02.b
+select -assert-count 0 top/genblk1.a
+select -assert-count 1 top/genblk02.b
+select -assert-count 1 top/g1[0].genblk1.a
+select -assert-count 1 top/genblk4[0].genblk1.a
+select -assert-count 1 top/genblk5.a