diff --git a/CHANGELOG b/CHANGELOG
index a908096e3..650a6aeaf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -53,7 +53,9 @@ Yosys 0.9 .. Yosys 0.9-dev
     - Added support for flip-flops with synchronous reset to synth_xilinx
     - Added support for flip-flops with reset and enable to synth_xilinx
     - Added "check -mapped"
-    - Added checking of SystemVerilog always block types (always_comb, always_latch and always_ff)
+    - Added checking of SystemVerilog always block types (always_comb,
+      always_latch and always_ff)
+    - Added support for SystemVerilog wildcard port connections (.*)
     - Added "xilinx_dffopt" pass
     - Added "scratchpad" pass
     - Added "abc9 -dff"
diff --git a/README.md b/README.md
index 77e9410da..327d407f9 100644
--- a/README.md
+++ b/README.md
@@ -387,6 +387,10 @@ Verilog Attributes and non-standard features
   according to the type of the always. These are checked for correctness in
   ``proc_dlatch``.
 
+- The cell attribute ``wildcard_port_conns`` represents wildcard port
+  connections (SystemVerilog ``.*``). These are resolved to concrete
+  connections to matching wires in ``hierarchy``.  
+
 - In addition to the ``(* ... *)`` attribute syntax, Yosys supports
   the non-standard ``{* ... *}`` attribute syntax to set default attributes
   for everything that comes after the ``{* ... *}`` statement. (Reset
diff --git a/backends/json/json.cc b/backends/json/json.cc
index 107009ee4..5c67cb857 100644
--- a/backends/json/json.cc
+++ b/backends/json/json.cc
@@ -33,6 +33,7 @@ struct JsonWriter
 	std::ostream &f;
 	bool use_selection;
 	bool aig_mode;
+	bool compat_int_mode;
 
 	Design *design;
 	Module *module;
@@ -42,8 +43,9 @@ struct JsonWriter
 	dict<SigBit, string> sigids;
 	pool<Aig> aig_models;
 
-	JsonWriter(std::ostream &f, bool use_selection, bool aig_mode) :
-			f(f), use_selection(use_selection), aig_mode(aig_mode) { }
+	JsonWriter(std::ostream &f, bool use_selection, bool aig_mode, bool compat_int_mode) :
+			f(f), use_selection(use_selection), aig_mode(aig_mode),
+			compat_int_mode(compat_int_mode) { }
 
 	string get_string(string str)
 	{
@@ -102,8 +104,7 @@ struct JsonWriter
 			if (state < 2)
 				str += " ";
 			f << get_string(str);
-		} else
-		if (GetSize(value) == 32 && value.is_fully_def()) {
+		} else if (compat_int_mode && GetSize(value) == 32 && value.is_fully_def()) {
 			if ((value.flags & RTLIL::ConstFlags::CONST_FLAG_SIGNED) != 0)
 				f << stringf("%d", value.as_int());
 			else
@@ -294,6 +295,10 @@ struct JsonBackend : public Backend {
 		log("    -aig\n");
 		log("        include AIG models for the different gate types\n");
 		log("\n");
+		log("    -compat-int\n");
+		log("        emit 32-bit fully-defined parameter values directly\n");
+		log("        as JSON numbers (for compatibility with old parsers)\n");
+		log("\n");
 		log("\n");
 		log("The general syntax of the JSON output created by this command is as follows:\n");
 		log("\n");
@@ -368,10 +373,9 @@ struct JsonBackend : public Backend {
 		log("connected to a constant driver are denoted as string \"0\", \"1\", \"x\", or\n");
 		log("\"z\" instead of a number.\n");
 		log("\n");
-		log("Numeric 32-bit parameter and attribute values are written as decimal values.\n");
-		log("Bit verctors of different sizes, or ones containing 'x' or 'z' bits, are written\n");
-		log("as string holding the binary representation of the value. Strings are written\n");
-		log("as strings, with an appended blank in cases of strings of the form /[01xz]* */.\n");
+		log("Bit vectors (including integers) are written as string holding the binary");
+		log("representation of the value. Strings are written as strings, with an appended");
+		log("blank in cases of strings of the form /[01xz]* */.\n");
 		log("\n");
 		log("For example the following Verilog code:\n");
 		log("\n");
@@ -495,6 +499,7 @@ struct JsonBackend : public Backend {
 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
 	{
 		bool aig_mode = false;
+		bool compat_int_mode = false;
 
 		size_t argidx;
 		for (argidx = 1; argidx < args.size(); argidx++)
@@ -503,13 +508,17 @@ struct JsonBackend : public Backend {
 				aig_mode = true;
 				continue;
 			}
+			if (args[argidx] == "-compat-int") {
+				compat_int_mode = true;
+				continue;
+			}
 			break;
 		}
 		extra_args(f, filename, args, argidx);
 
 		log_header(design, "Executing JSON backend.\n");
 
-		JsonWriter json_writer(*f, false, aig_mode);
+		JsonWriter json_writer(*f, false, aig_mode, compat_int_mode);
 		json_writer.write_design(design);
 	}
 } JsonBackend;
@@ -530,6 +539,10 @@ struct JsonPass : public Pass {
 		log("    -aig\n");
 		log("        also include AIG models for the different gate types\n");
 		log("\n");
+		log("    -compat-int\n");
+		log("        emit 32-bit fully-defined parameter values directly\n");
+		log("        as JSON numbers (for compatibility with old parsers)\n");
+		log("\n");
 		log("See 'help write_json' for a description of the JSON format used.\n");
 		log("\n");
 	}
@@ -537,6 +550,7 @@ struct JsonPass : public Pass {
 	{
 		std::string filename;
 		bool aig_mode = false;
+		bool compat_int_mode = false;
 
 		size_t argidx;
 		for (argidx = 1; argidx < args.size(); argidx++)
@@ -549,6 +563,10 @@ struct JsonPass : public Pass {
 				aig_mode = true;
 				continue;
 			}
+			if (args[argidx] == "-compat-int") {
+				compat_int_mode = true;
+				continue;
+			}
 			break;
 		}
 		extra_args(args, argidx, design);
@@ -569,7 +587,7 @@ struct JsonPass : public Pass {
 			f = &buf;
 		}
 
-		JsonWriter json_writer(*f, true, aig_mode);
+		JsonWriter json_writer(*f, true, aig_mode, compat_int_mode);
 		json_writer.write_design(design);
 
 		if (!filename.empty()) {
diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h
index 918d178c7..14e1cec5e 100644
--- a/frontends/ast/ast.h
+++ b/frontends/ast/ast.h
@@ -244,6 +244,7 @@ namespace AST
 		void replace_variables(std::map<std::string, varinfo_t> &variables, AstNode *fcall);
 		AstNode *eval_const_function(AstNode *fcall);
 		bool is_simple_const_expr();
+		std::string process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint);
 
 		// create a human-readable text representation of the AST (for debugging)
 		void dumpAst(FILE *f, std::string indent) const;
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index 1b6618cd8..52f157c6e 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -41,6 +41,103 @@ YOSYS_NAMESPACE_BEGIN
 using namespace AST;
 using namespace AST_INTERNAL;
 
+// Process a format string and arguments for $display, $write, $sprintf, etc
+
+std::string AstNode::process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint) {
+	// Other arguments are placeholders. Process the string as we go through it
+	std::string sout;
+	for (size_t i = 0; i < sformat.length(); i++)
+	{
+		// format specifier
+		if (sformat[i] == '%')
+		{
+			// If there's no next character, that's a problem
+			if (i+1 >= sformat.length())
+				log_file_error(filename, linenum, "System task `%s' called with `%%' at end of string.\n", str.c_str());
+
+			char cformat = sformat[++i];
+
+			// %% is special, does not need a matching argument
+			if (cformat == '%')
+			{
+				sout += '%';
+				continue;
+			}
+
+			// Simplify the argument
+			AstNode *node_arg = nullptr;
+
+			// Everything from here on depends on the format specifier
+			switch (cformat)
+			{
+				case 's':
+				case 'S':
+				case 'd':
+				case 'D':
+				case 'x':
+				case 'X':
+					if (next_arg >= GetSize(children))
+						log_file_error(filename, linenum, "Missing argument for %%%c format specifier in system task `%s'.\n",
+								cformat, str.c_str());
+
+					node_arg = children[next_arg++];
+					while (node_arg->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (node_arg->type != AST_CONSTANT)
+						log_file_error(filename, linenum, "Failed to evaluate system task `%s' with non-constant argument.\n", str.c_str());
+					break;
+
+				case 'm':
+				case 'M':
+					break;
+
+				default:
+					log_file_error(filename, linenum, "System task `%s' called with invalid/unsupported format specifier.\n", str.c_str());
+					break;
+			}
+
+			switch (cformat)
+			{
+				case 's':
+				case 'S':
+					sout += node_arg->bitsAsConst().decode_string();
+					break;
+
+				case 'd':
+				case 'D':
+					{
+						char tmp[128];
+						snprintf(tmp, sizeof(tmp), "%d", node_arg->bitsAsConst().as_int());
+						sout += tmp;
+					}
+					break;
+
+				case 'x':
+				case 'X':
+					{
+						char tmp[128];
+						snprintf(tmp, sizeof(tmp), "%x", node_arg->bitsAsConst().as_int());
+						sout += tmp;
+					}
+					break;
+
+				case 'm':
+				case 'M':
+					sout += log_id(current_module->name);
+					break;
+
+				default:
+					log_abort();
+			}
+		}
+
+		// not a format specifier
+		else
+			sout += sformat[i];
+	}
+	return sout;
+}
+
+
 // 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().
@@ -216,99 +313,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 		if (node_string->type != AST_CONSTANT)
 			log_file_error(filename, linenum, "Failed to evaluate system task `%s' with non-constant 1st argument.\n", str.c_str());
 		std::string sformat = node_string->bitsAsConst().decode_string();
-
-		// Other arguments are placeholders. Process the string as we go through it
-		std::string sout;
-		int next_arg = 1;
-		for (size_t i = 0; i < sformat.length(); i++)
-		{
-			// format specifier
-			if (sformat[i] == '%')
-			{
-				// If there's no next character, that's a problem
-				if (i+1 >= sformat.length())
-					log_file_error(filename, linenum, "System task `%s' called with `%%' at end of string.\n", str.c_str());
-
-				char cformat = sformat[++i];
-
-				// %% is special, does not need a matching argument
-				if (cformat == '%')
-				{
-					sout += '%';
-					continue;
-				}
-
-				// Simplify the argument
-				AstNode *node_arg = nullptr;
-
-				// Everything from here on depends on the format specifier
-				switch (cformat)
-				{
-					case 's':
-					case 'S':
-					case 'd':
-					case 'D':
-					case 'x':
-					case 'X':
-						if (next_arg >= GetSize(children))
-							log_file_error(filename, linenum, "Missing argument for %%%c format specifier in system task `%s'.\n",
-									cformat, str.c_str());
-
-						node_arg = children[next_arg++];
-						while (node_arg->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
-						if (node_arg->type != AST_CONSTANT)
-							log_file_error(filename, linenum, "Failed to evaluate system task `%s' with non-constant argument.\n", str.c_str());
-						break;
-
-					case 'm':
-					case 'M':
-						break;
-
-					default:
-						log_file_error(filename, linenum, "System task `%s' called with invalid/unsupported format specifier.\n", str.c_str());
-						break;
-				}
-
-				switch (cformat)
-				{
-					case 's':
-					case 'S':
-						sout += node_arg->bitsAsConst().decode_string();
-						break;
-
-					case 'd':
-					case 'D':
-						{
-							char tmp[128];
-							snprintf(tmp, sizeof(tmp), "%d", node_arg->bitsAsConst().as_int());
-							sout += tmp;
-						}
-						break;
-
-					case 'x':
-					case 'X':
-						{
-							char tmp[128];
-							snprintf(tmp, sizeof(tmp), "%x", node_arg->bitsAsConst().as_int());
-							sout += tmp;
-						}
-						break;
-
-					case 'm':
-					case 'M':
-						sout += log_id(current_module->name);
-						break;
-
-					default:
-						log_abort();
-				}
-			}
-
-			// not a format specifier
-			else
-				sout += sformat[i];
-		}
-
+		std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint);
 		// Finally, print the message (only include a \n for $display, not for $write)
 		log("%s", sout.c_str());
 		if (str == "$display")
@@ -2244,6 +2249,17 @@ skip_dynamic_range_lvalue_expansion:;
 				goto apply_newNode;
 			}
 
+			if (str == "\\$sformatf") {
+				AstNode *node_string = children[0];
+				while (node_string->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+				if (node_string->type != AST_CONSTANT)
+					log_file_error(filename, linenum, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str());
+				std::string sformat = node_string->bitsAsConst().decode_string();
+				std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint);
+				newNode = AstNode::mkconst_str(sout);
+				goto apply_newNode;
+			}
+
 			if (current_scope.count(str) != 0 && current_scope[str]->type == AST_DPI_FUNCTION)
 			{
 				AstNode *dpi_decl = current_scope[str];
diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l
index ca23df3e8..9b43c250e 100644
--- a/frontends/verilog/verilog_lexer.l
+++ b/frontends/verilog/verilog_lexer.l
@@ -431,6 +431,8 @@ import[ \t\r\n]+\"(DPI|DPI-C)\"[ \t\r\n]+function[ \t\r\n]+ {
 "+:" { return TOK_POS_INDEXED; }
 "-:" { return TOK_NEG_INDEXED; }
 
+".*" { return TOK_WILDCARD_CONNECT; }
+
 [-+]?[=*]> {
 	if (!specify_mode) REJECT;
 	frontend_verilog_yylval.string = new std::string(yytext);
diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y
index a30935e0a..2c7304cc4 100644
--- a/frontends/verilog/verilog_parser.y
+++ b/frontends/verilog/verilog_parser.y
@@ -138,7 +138,7 @@ struct specify_rise_fall {
 %token ATTR_BEGIN ATTR_END DEFATTR_BEGIN DEFATTR_END
 %token TOK_MODULE TOK_ENDMODULE TOK_PARAMETER TOK_LOCALPARAM TOK_DEFPARAM
 %token TOK_PACKAGE TOK_ENDPACKAGE TOK_PACKAGESEP
-%token TOK_INTERFACE TOK_ENDINTERFACE TOK_MODPORT TOK_VAR
+%token TOK_INTERFACE TOK_ENDINTERFACE TOK_MODPORT TOK_VAR TOK_WILDCARD_CONNECT
 %token TOK_INPUT TOK_OUTPUT TOK_INOUT TOK_WIRE TOK_WAND TOK_WOR TOK_REG TOK_LOGIC
 %token TOK_INTEGER TOK_SIGNED TOK_ASSIGN TOK_ALWAYS TOK_INITIAL
 %token TOK_ALWAYS_FF TOK_ALWAYS_COMB TOK_ALWAYS_LATCH
@@ -1580,6 +1580,11 @@ cell_port:
 		node->children.back()->str = *$3;
 		delete $3;
 		free_attr($1);
+	} |
+	attr TOK_WILDCARD_CONNECT {
+		if (!sv_mode)
+			frontend_verilog_yyerror("Wildcard port connections are only supported in SystemVerilog mode.");
+		astbuf2->attributes[ID(wildcard_port_conns)] = AstNode::mkconst_int(1, false);
 	};
 
 always_comb_or_latch:
diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc
index d8a628448..fa4a8ea29 100644
--- a/passes/hierarchy/hierarchy.cc
+++ b/passes/hierarchy/hierarchy.cc
@@ -548,6 +548,19 @@ RTLIL::Module *check_if_top_has_changed(Design *design, Module *top_mod)
 	return NULL;
 }
 
+// Find a matching wire for an implicit port connection; traversing generate block scope
+RTLIL::Wire *find_implicit_port_wire(Module *module, Cell *cell, const std::string& port)
+{
+	const std::string &cellname = cell->name.str();
+	size_t idx = cellname.size();
+	while ((idx = cellname.find_last_of('.', idx-1)) != std::string::npos) {
+		Wire *found = module->wire(cellname.substr(0, idx+1) + port.substr(1));
+		if (found != nullptr)
+			return found;
+	}
+	return module->wire(port);
+}
+
 struct HierarchyPass : public Pass {
 	HierarchyPass() : Pass("hierarchy", "check, expand and clean up design hierarchy") { }
 	void help() YS_OVERRIDE
@@ -970,15 +983,71 @@ struct HierarchyPass : public Pass {
 			}
 		}
 
+		// Determine default values
+		dict<IdString, dict<IdString, Const>> defaults_db;
 		if (!nodefaults)
 		{
-			dict<IdString, dict<IdString, Const>> defaults_db;
-
 			for (auto module : design->modules())
 				for (auto wire : module->wires())
 					if (wire->port_input && wire->attributes.count("\\defaultvalue"))
 						defaults_db[module->name][wire->name] = wire->attributes.at("\\defaultvalue");
+		}
+		// Process SV implicit wildcard port connections
+		std::set<Module*> blackbox_derivatives;
+		std::vector<Module*> design_modules = design->modules();
 
+		for (auto module : design_modules)
+		{
+			for (auto cell : module->cells())
+			{
+				if (!cell->get_bool_attribute(ID(wildcard_port_conns)))
+					continue;
+				Module *m = design->module(cell->type);
+
+				if (m == nullptr)
+					log_error("Cell %s.%s (%s) has implicit port connections but the module it instantiates is unknown.\n",
+							RTLIL::id2cstr(module->name), RTLIL::id2cstr(cell->name), RTLIL::id2cstr(cell->type));
+
+				// Need accurate port widths for error checking; so must derive blackboxes with dynamic port widths
+				if (m->get_blackbox_attribute() && !cell->parameters.empty() && m->get_bool_attribute("\\dynports")) {
+					IdString new_m_name = m->derive(design, cell->parameters, true);
+					if (new_m_name.empty())
+						continue;
+					if (new_m_name != m->name) {
+						m = design->module(new_m_name);
+						blackbox_derivatives.insert(m);
+					}
+				}
+
+				auto old_connections = cell->connections();
+				for (auto wire : m->wires()) {
+					// Find ports of the module that aren't explicitly connected
+					if (!wire->port_input && !wire->port_output)
+						continue;
+					if (old_connections.count(wire->name))
+						continue;
+					// Make sure a wire of correct name exists in the parent
+					Wire* parent_wire = find_implicit_port_wire(module, cell, wire->name.str());
+
+					// Missing wires are OK when a default value is set
+					if (!nodefaults && parent_wire == nullptr && defaults_db.count(cell->type) && defaults_db.at(cell->type).count(wire->name))
+						continue;
+
+					if (parent_wire == nullptr)
+						log_error("No matching wire for implicit port connection `%s' of cell %s.%s (%s).\n",
+								RTLIL::id2cstr(wire->name), RTLIL::id2cstr(module->name), RTLIL::id2cstr(cell->name), RTLIL::id2cstr(cell->type));
+					if (parent_wire->width != wire->width)
+						log_error("Width mismatch between wire (%d bits) and port (%d bits) for implicit port connection `%s' of cell %s.%s (%s).\n",
+								parent_wire->width, wire->width,
+								RTLIL::id2cstr(wire->name), RTLIL::id2cstr(module->name), RTLIL::id2cstr(cell->name), RTLIL::id2cstr(cell->type));
+					cell->setPort(wire->name, parent_wire);
+				}
+				cell->attributes.erase(ID(wildcard_port_conns));
+			}
+		}
+
+		if (!nodefaults)
+		{
 			for (auto module : design->modules())
 				for (auto cell : module->cells())
 				{
@@ -1000,9 +1069,6 @@ struct HierarchyPass : public Pass {
 				}
 		}
 
-		std::set<Module*> blackbox_derivatives;
-		std::vector<Module*> design_modules = design->modules();
-
 		for (auto module : design_modules)
 		{
 			pool<Wire*> wand_wor_index;
diff --git a/passes/pmgen/xilinx_dsp.cc b/passes/pmgen/xilinx_dsp.cc
index 81c3c57c4..ae7967d7c 100644
--- a/passes/pmgen/xilinx_dsp.cc
+++ b/passes/pmgen/xilinx_dsp.cc
@@ -767,6 +767,9 @@ struct XilinxDspPass : public Pass {
 		log("to a maximum length of 20 cells, corresponding to the smallest Xilinx 7 Series\n");
 		log("device.\n");
 		log("\n");
+		log("This pass is a no-op if the scratchpad variable 'xilinx_dsp.multonly' is set\n");
+		log("to 1.\n");
+		log("\n");
 		log("\n");
 		log("Experimental feature: addition/subtractions less than 12 or 24 bits with the\n");
 		log("'(* use_dsp=\"simd\" *)' attribute attached to the output wire or attached to\n");
@@ -805,6 +808,10 @@ struct XilinxDspPass : public Pass {
 			family = "xcu";
 
 		for (auto module : design->selected_modules()) {
+
+			if (design->scratchpad_get_bool("xilinx_dsp.multonly"))
+				continue;
+
 			// Experimental feature: pack $add/$sub cells with
 			//   (* use_dsp48="simd" *) into DSP48E1's using its
 			//   SIMD feature
diff --git a/techlibs/xilinx/lutrams.txt b/techlibs/xilinx/lutrams.txt
index 29f6b05cc..faf66bc18 100644
--- a/techlibs/xilinx/lutrams.txt
+++ b/techlibs/xilinx/lutrams.txt
@@ -153,7 +153,7 @@ endmatch
 
 match $__XILINX_RAM32X2Q
   min bits 5
-  min rports 3
+  min rports 2
   min wports 1
   make_outreg
   or_next_if_better
@@ -161,7 +161,7 @@ endmatch
 
 match $__XILINX_RAM64X1Q
   min bits 5
-  min rports 3
+  min rports 2
   min wports 1
   make_outreg
 endmatch
diff --git a/tests/various/sformatf.ys b/tests/various/sformatf.ys
new file mode 100644
index 000000000..66d6b0dbe
--- /dev/null
+++ b/tests/various/sformatf.ys
@@ -0,0 +1,12 @@
+read_verilog <<EOT
+
+module top;
+	localparam a = $sformatf("0x%x", 8'h5A);
+	localparam b = $sformatf("%d", 4'b011);
+	generate
+		if (a != "0x5a") $error("a incorrect!");
+		if (b != "3") $error("b incorrect!");
+	endgenerate
+endmodule
+
+EOT
diff --git a/tests/various/sv_implicit_ports.sh b/tests/various/sv_implicit_ports.sh
new file mode 100755
index 000000000..9a01447f7
--- /dev/null
+++ b/tests/various/sv_implicit_ports.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+
+trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR
+
+# Simple case
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+	assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	wire [7:0] b = 8'd42;
+	add add_i(.*);
+endmodule
+EOT
+
+# Generate block
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	generate
+	if (1) begin:ablock
+		wire [7:0] b = 8'd42;
+		add add_i(.*);
+	end
+	endgenerate
+endmodule
+EOT
+
+# Missing wire
+((../../yosys -f "verilog -sv" -qp "hierarchy -top top" - || true) <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+	assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	add add_i(.*);
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: No matching wire for implicit port connection \`b' of cell top.add_i (add)." > /dev/null
+
+# Incorrectly sized wire
+((../../yosys -f "verilog -sv" -qp "hierarchy -top top" - || true) <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+	assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	wire [6:0] b = 6'd42;
+	add add_i(.*);
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: Width mismatch between wire (7 bits) and port (8 bits) for implicit port connection \`b' of cell top.add_i (add)." > /dev/null
+
+# Defaults
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add(input [7:0] a = 8'd00, input [7:0] b = 8'd01, output [7:0] q);
+assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	add add_i(.*);
+endmodule
+EOT
+
+# Parameterised module
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add #(parameter N=3) (input [N-1:0] a = 8'd00, input [N-1:0] b = 8'd01, output [N-1:0] q);
+assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	add #(.N(8)) add_i(.*);
+endmodule
+EOT
+
+# Parameterised blackbox module
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:add" - <<EOT
+(* blackbox *)
+module add #(parameter N=3) (input [N-1:0] a, b, output [N-1:0] q);
+endmodule
+
+module top(input [7:0] a, b, output [7:0] q);
+	add #(.N(8)) add_i(.*);
+endmodule
+EOT
+
+# Parameterised blackbox module - incorrect width
+((../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:add" - || true) <<EOT
+(* blackbox *)
+module add #(parameter N=3) (input [N-1:0] a, b, output [N-1:0] q);
+endmodule
+
+module top(input [7:0] a, b, output [7:0] q);
+	add #(.N(6)) add_i(.*);
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: Width mismatch between wire (8 bits) and port (6 bits) for implicit port connection \`q' of cell top.add_i (add)." > /dev/null
+
+# Mixed implicit and explicit 1
+../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+	assign q = a + b;
+endmodule
+
+module top(input [7:0] a, output [7:0] q);
+	add add_i(.b(8'd42), .*);
+endmodule
+EOT
+
+# Mixed implicit and explicit 2
+(../../yosys -f "verilog -sv" -qp "prep -flatten -top top; select -assert-count 1 t:\$add" - <<EOT
+module add(input [7:0] a, input [7:0] b, output [7:0] q);
+	assign q = a + b;
+endmodule
+
+module top(input [7:0] a, input [9:0] b, output [7:0] q);
+	add add_i(.b, .*);
+endmodule
+EOT
+) 2>&1 | grep -F "Warning: Resizing cell port top.add_i.b from 10 bits to 8 bits." > /dev/null