diff --git a/CHANGELOG b/CHANGELOG
index 481ba266e..ca7196091 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -55,10 +55,12 @@ Yosys 0.9 .. Yosys 0.9-dev
     - Added "check -mapped"
     - 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"
     - Added "synth_xilinx -dff"
+    - Added "opt_lut_ins" pass
 
 Yosys 0.8 .. Yosys 0.9
 ----------------------
diff --git a/README.md b/README.md
index 77e9410da..e4301e7bf 100644
--- a/README.md
+++ b/README.md
@@ -373,10 +373,15 @@ Verilog Attributes and non-standard features
   `abc9` to preserve the integrity of carry-chains. Specifying this attribute
   onto a bus port will affect only its most significant bit.
 
-- The port attribute ``abc9_arrival`` specifies an integer (for output ports
-  only) to be used as the arrival time of this sequential port. It can be used,
-  for example, to specify the clk-to-Q delay of a flip-flop for consideration
-  during `abc9` techmapping.
+- The output port attribute ``abc9_arrival`` specifies an integer, or a string
+  of space-separated integers to be used as the arrival time of this blackbox
+  port. It can be used, for example, to specify the clk-to-Q delay of a flip-
+  flop output for consideration during `abc9` techmapping.
+
+- The input port attribute ``abc9_required`` specifies an integer, or a string
+  of space-separated integers to be used as the required time of this blackbox
+  port. It can be used, for example, to specify the setup-time of a flip-flop
+  input for consideration during `abc9` techmapping.
 
 - The module attribute ``abc9_flop`` is a boolean marking the module as a
   flip-flop. This allows `abc9` to analyse its contents in order to perform
@@ -387,6 +392,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/aiger/xaiger.cc b/backends/aiger/xaiger.cc
index a6c87159d..76b7efbfc 100644
--- a/backends/aiger/xaiger.cc
+++ b/backends/aiger/xaiger.cc
@@ -93,7 +93,6 @@ struct XAigerWriter
 	dict<SigBit, int> ordered_outputs;
 
 	vector<Cell*> box_list;
-	dict<IdString, std::vector<IdString>> box_ports;
 
 	int mkgate(int a0, int a1)
 	{
@@ -157,7 +156,6 @@ struct XAigerWriter
 			if (wire->get_bool_attribute(ID::keep))
 				sigmap.add(wire);
 
-
 		for (auto wire : module->wires())
 			for (int i = 0; i < GetSize(wire); i++)
 			{
@@ -175,108 +173,117 @@ struct XAigerWriter
 				undriven_bits.insert(bit);
 				unused_bits.insert(bit);
 
-				if (wire->port_input)
+				bool keep = wire->get_bool_attribute(ID::keep);
+				if (wire->port_input || keep)
 					input_bits.insert(bit);
 
-				if (wire->port_output) {
+				if (wire->port_output || keep) {
 					if (bit != wirebit)
 						alias_map[wirebit] = bit;
 					output_bits.insert(wirebit);
 				}
 			}
 
-		// TODO: Speed up toposort -- ultimately we care about
-		//       box ordering, but not individual AIG cells
-		dict<SigBit, pool<IdString>> bit_drivers, bit_users;
-		TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
-		bool abc9_box_seen = false;
-
-		for (auto cell : module->selected_cells()) {
-			if (cell->type == "$_NOT_")
-			{
-				SigBit A = sigmap(cell->getPort("\\A").as_bit());
-				SigBit Y = sigmap(cell->getPort("\\Y").as_bit());
-				unused_bits.erase(A);
-				undriven_bits.erase(Y);
-				not_map[Y] = A;
-				toposort.node(cell->name);
-				bit_users[A].insert(cell->name);
-				bit_drivers[Y].insert(cell->name);
-				continue;
-			}
-
-			if (cell->type == "$_AND_")
-			{
-				SigBit A = sigmap(cell->getPort("\\A").as_bit());
-				SigBit B = sigmap(cell->getPort("\\B").as_bit());
-				SigBit Y = sigmap(cell->getPort("\\Y").as_bit());
-				unused_bits.erase(A);
-				unused_bits.erase(B);
-				undriven_bits.erase(Y);
-				and_map[Y] = make_pair(A, B);
-				toposort.node(cell->name);
-				bit_users[A].insert(cell->name);
-				bit_users[B].insert(cell->name);
-				bit_drivers[Y].insert(cell->name);
-				continue;
-			}
-
-			if (cell->type == "$__ABC9_FF_" &&
-                                        // The presence of an abc9_mergeability attribute indicates
-                                        //   that we do want to pass this flop to ABC
-                                        cell->attributes.count("\\abc9_mergeability"))
-			{
-				SigBit D = sigmap(cell->getPort("\\D").as_bit());
-				SigBit Q = sigmap(cell->getPort("\\Q").as_bit());
-				unused_bits.erase(D);
-				undriven_bits.erase(Q);
-				alias_map[Q] = D;
-				auto r YS_ATTRIBUTE(unused) = ff_bits.insert(std::make_pair(D, cell));
-				log_assert(r.second);
-				continue;
-			}
-
+		dict<IdString,dict<IdString,std::vector<int>>> arrivals_cache;
+		for (auto cell : module->cells()) {
 			RTLIL::Module* inst_module = module->design->module(cell->type);
-			if (inst_module) {
-				bool abc9_box = inst_module->attributes.count("\\abc9_box_id");
-				bool abc9_flop = inst_module->get_bool_attribute("\\abc9_flop");
-				if (abc9_box && cell->get_bool_attribute("\\abc9_keep"))
-					abc9_box = false;
+			if (!cell->has_keep_attr()) {
+				if (cell->type == "$_NOT_")
+				{
+					SigBit A = sigmap(cell->getPort("\\A").as_bit());
+					SigBit Y = sigmap(cell->getPort("\\Y").as_bit());
+					unused_bits.erase(A);
+					undriven_bits.erase(Y);
+					not_map[Y] = A;
+					continue;
+				}
 
-				for (const auto &conn : cell->connections()) {
-					auto port_wire = inst_module->wire(conn.first);
+				if (cell->type == "$_AND_")
+				{
+					SigBit A = sigmap(cell->getPort("\\A").as_bit());
+					SigBit B = sigmap(cell->getPort("\\B").as_bit());
+					SigBit Y = sigmap(cell->getPort("\\Y").as_bit());
+					unused_bits.erase(A);
+					unused_bits.erase(B);
+					undriven_bits.erase(Y);
+					and_map[Y] = make_pair(A, B);
+					continue;
+				}
 
-					if (abc9_box) {
-						// Ignore inout for the sake of topographical ordering
-						if (port_wire->port_input && !port_wire->port_output)
-							for (auto bit : sigmap(conn.second))
-								bit_users[bit].insert(cell->name);
-						if (port_wire->port_output)
-							for (auto bit : sigmap(conn.second))
-								bit_drivers[bit].insert(cell->name);
+				if (cell->type == "$__ABC9_FF_" &&
+						// The presence of an abc9_mergeability attribute indicates
+						//   that we do want to pass this flop to ABC
+						cell->attributes.count("\\abc9_mergeability"))
+				{
+					SigBit D = sigmap(cell->getPort("\\D").as_bit());
+					SigBit Q = sigmap(cell->getPort("\\Q").as_bit());
+					unused_bits.erase(D);
+					undriven_bits.erase(Q);
+					alias_map[Q] = D;
+					auto r YS_ATTRIBUTE(unused) = ff_bits.insert(std::make_pair(D, cell));
+					log_assert(r.second);
+					if (input_bits.erase(Q))
+						log_assert(Q.wire->attributes.count(ID::keep));
+					continue;
+				}
 
+				if (inst_module) {
+					bool abc9_flop = false;
+					auto it = cell->attributes.find("\\abc9_box_seq");
+					if (it != cell->attributes.end()) {
+						int abc9_box_seq = it->second.as_int();
+						if (GetSize(box_list) <= abc9_box_seq)
+							box_list.resize(abc9_box_seq+1);
+						box_list[abc9_box_seq] = cell;
+						// Only flop boxes may have arrival times
+						//   (all others are combinatorial)
+						abc9_flop = inst_module->get_bool_attribute("\\abc9_flop");
 						if (!abc9_flop)
 							continue;
 					}
 
-					if (port_wire->port_output) {
-						int arrival = 0;
-						auto it = port_wire->attributes.find("\\abc9_arrival");
-						if (it != port_wire->attributes.end()) {
-							if (it->second.flags != 0)
-								log_error("Attribute 'abc9_arrival' on port '%s' of module '%s' is not an integer.\n", log_id(port_wire), log_id(cell->type));
-							arrival = it->second.as_int();
-						}
-						if (arrival)
-							for (auto bit : sigmap(conn.second))
-								arrival_times[bit] = arrival;
-					}
-				}
+					auto &cell_arrivals = arrivals_cache[cell->type];
+					for (const auto &conn : cell->connections()) {
+						auto port_wire = inst_module->wire(conn.first);
+						if (!port_wire->port_output)
+							continue;
 
-				if (abc9_box) {
-					abc9_box_seen = true;
-					toposort.node(cell->name);
-					continue;
+						auto r = cell_arrivals.insert(conn.first);
+						auto &arrivals = r.first->second;
+						if (r.second) {
+							auto it = port_wire->attributes.find("\\abc9_arrival");
+							if (it == port_wire->attributes.end())
+								continue;
+							if (it->second.flags == 0)
+								arrivals.emplace_back(it->second.as_int());
+							else
+								for (const auto &tok : split_tokens(it->second.decode_string()))
+									arrivals.push_back(atoi(tok.c_str()));
+						}
+
+						if (arrivals.empty())
+							continue;
+
+						if (GetSize(arrivals) > 1 && GetSize(arrivals) != GetSize(port_wire))
+							log_error("%s.%s is %d bits wide but abc9_arrival = %s has %d value(s)!\n", log_id(cell->type), log_id(conn.first),
+									GetSize(port_wire), log_signal(it->second), GetSize(arrivals));
+
+						auto jt = arrivals.begin();
+#ifndef NDEBUG
+						if (ys_debug(1)) {
+							static std::set<std::pair<IdString,IdString>> seen;
+							if (seen.emplace(cell->type, conn.first).second) log("%s.%s abc9_arrival = %d\n", log_id(cell->type), log_id(conn.first), *jt);
+						}
+#endif
+						for (auto bit : sigmap(conn.second)) {
+							arrival_times[bit] = *jt;
+							if (arrivals.size() > 1)
+								jt++;
+						}
+					}
+
+					if (abc9_flop)
+						continue;
 				}
 			}
 
@@ -293,6 +300,9 @@ struct XAigerWriter
 					for (auto b : c.second) {
 						Wire *w = b.wire;
 						if (!w) continue;
+						// Do not add as PO if bit is already a PI
+						if (input_bits.count(b))
+							continue;
 						if (!w->port_output || !cell_known) {
 							SigBit I = sigmap(b);
 							if (I != b)
@@ -305,138 +315,54 @@ struct XAigerWriter
 			//log_warning("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell));
 		}
 
-		if (abc9_box_seen) {
-			for (auto &it : bit_users)
-				if (bit_drivers.count(it.first))
-					for (auto driver_cell : bit_drivers.at(it.first))
-					for (auto user_cell : it.second)
-						toposort.edge(driver_cell, user_cell);
+		dict<IdString, std::vector<IdString>> box_ports;
+		for (auto cell : box_list) {
+			log_assert(cell);
 
-#if 0
-			toposort.analyze_loops = true;
-#endif
-			bool no_loops YS_ATTRIBUTE(unused) = toposort.sort();
-#if 0
-			unsigned i = 0;
-			for (auto &it : toposort.loops) {
-				log("  loop %d\n", i++);
-				for (auto cell_name : it) {
-					auto cell = module->cell(cell_name);
-					log_assert(cell);
-					log("\t%s (%s @ %s)\n", log_id(cell), log_id(cell->type), cell->get_src_attribute().c_str());
-				}
-			}
-#endif
-			log_assert(no_loops);
+			RTLIL::Module* box_module = module->design->module(cell->type);
+			log_assert(box_module);
+			log_assert(box_module->attributes.count("\\abc9_box_id") || box_module->get_bool_attribute("\\abc9_flop"));
 
-			for (auto cell_name : toposort.sorted) {
-				RTLIL::Cell *cell = module->cell(cell_name);
-				log_assert(cell);
-
-				RTLIL::Module* box_module = module->design->module(cell->type);
-				if (!box_module || !box_module->attributes.count("\\abc9_box_id"))
-					continue;
-
-				bool blackbox = box_module->get_blackbox_attribute(true /* ignore_wb */);
-
-				auto r = box_ports.insert(cell->type);
-				if (r.second) {
-					// Make carry in the last PI, and carry out the last PO
-					//   since ABC requires it this way
-					IdString carry_in, carry_out;
-					for (const auto &port_name : box_module->ports) {
-						auto w = box_module->wire(port_name);
-						log_assert(w);
-						if (w->get_bool_attribute("\\abc9_carry")) {
-							if (w->port_input) {
-								if (carry_in != IdString())
-									log_error("Module '%s' contains more than one 'abc9_carry' input port.\n", log_id(box_module));
-								carry_in = port_name;
-							}
-							if (w->port_output) {
-								if (carry_out != IdString())
-									log_error("Module '%s' contains more than one 'abc9_carry' output port.\n", log_id(box_module));
-								carry_out = port_name;
-							}
-						}
-						else
-							r.first->second.push_back(port_name);
-					}
-
-					if (carry_in != IdString() && carry_out == IdString())
-						log_error("Module '%s' contains an 'abc9_carry' input port but no output port.\n", log_id(box_module));
-					if (carry_in == IdString() && carry_out != IdString())
-						log_error("Module '%s' contains an 'abc9_carry' output port but no input port.\n", log_id(box_module));
-					if (carry_in != IdString()) {
-						r.first->second.push_back(carry_in);
-						r.first->second.push_back(carry_out);
-					}
-				}
-
-				// Fully pad all unused input connections of this box cell with S0
-				// Fully pad all undriven output connections of this box cell with anonymous wires
-				for (auto port_name : r.first->second) {
+			auto r = box_ports.insert(cell->type);
+			if (r.second) {
+				// Make carry in the last PI, and carry out the last PO
+				//   since ABC requires it this way
+				IdString carry_in, carry_out;
+				for (const auto &port_name : box_module->ports) {
 					auto w = box_module->wire(port_name);
 					log_assert(w);
-					auto it = cell->connections_.find(port_name);
-					if (w->port_input) {
-						RTLIL::SigSpec rhs;
-						if (it != cell->connections_.end()) {
-							if (GetSize(it->second) < GetSize(w))
-								it->second.append(RTLIL::SigSpec(State::S0, GetSize(w)-GetSize(it->second)));
-							rhs = it->second;
+					if (w->get_bool_attribute("\\abc9_carry")) {
+						if (w->port_input) {
+							if (carry_in != IdString())
+								log_error("Module '%s' contains more than one 'abc9_carry' input port.\n", log_id(box_module));
+							carry_in = port_name;
 						}
-						else {
-							rhs = RTLIL::SigSpec(State::S0, GetSize(w));
-							cell->setPort(port_name, rhs);
-						}
-
-						for (auto b : rhs) {
-							SigBit I = sigmap(b);
-							if (b == RTLIL::Sx)
-								b = State::S0;
-							else if (I != b) {
-								if (I == RTLIL::Sx)
-									alias_map[b] = State::S0;
-								else
-									alias_map[b] = I;
-							}
-							co_bits.emplace_back(b);
-							unused_bits.erase(I);
-						}
-					}
-					if (w->port_output) {
-						RTLIL::SigSpec rhs;
-						auto it = cell->connections_.find(port_name);
-						if (it != cell->connections_.end()) {
-							if (GetSize(it->second) < GetSize(w))
-								it->second.append(module->addWire(NEW_ID, GetSize(w)-GetSize(it->second)));
-							rhs = it->second;
-						}
-						else {
-							Wire *wire = module->addWire(NEW_ID, GetSize(w));
-							if (blackbox)
-								wire->set_bool_attribute(ID(abc9_padding));
-							rhs = wire;
-							cell->setPort(port_name, rhs);
-						}
-
-						for (const auto &b : rhs.bits()) {
-							SigBit O = sigmap(b);
-							if (O != b)
-								alias_map[O] = b;
-							ci_bits.emplace_back(b);
-							undriven_bits.erase(O);
+						if (w->port_output) {
+							if (carry_out != IdString())
+								log_error("Module '%s' contains more than one 'abc9_carry' output port.\n", log_id(box_module));
+							carry_out = port_name;
 						}
 					}
+					else
+						r.first->second.push_back(port_name);
 				}
 
-				// Connect <cell>.abc9_ff.Q (inserted by abc9_map.v) as the last input to the flop box
-				if (box_module->get_bool_attribute("\\abc9_flop")) {
-					SigSpec rhs = module->wire(stringf("%s.abc9_ff.Q", cell->name.c_str()));
-					if (rhs.empty())
-						log_error("'%s.abc9_ff.Q' is not a wire present in module '%s'.\n", log_id(cell), log_id(module));
+				if (carry_in != IdString() && carry_out == IdString())
+					log_error("Module '%s' contains an 'abc9_carry' input port but no output port.\n", log_id(box_module));
+				if (carry_in == IdString() && carry_out != IdString())
+					log_error("Module '%s' contains an 'abc9_carry' output port but no input port.\n", log_id(box_module));
+				if (carry_in != IdString()) {
+					r.first->second.push_back(carry_in);
+					r.first->second.push_back(carry_out);
+				}
+			}
 
+			for (auto port_name : r.first->second) {
+				auto w = box_module->wire(port_name);
+				log_assert(w);
+				auto rhs = cell->connections_.at(port_name, SigSpec());
+				rhs.append(Const(State::Sx, GetSize(w)-GetSize(rhs)));
+				if (w->port_input)
 					for (auto b : rhs) {
 						SigBit I = sigmap(b);
 						if (b == RTLIL::Sx)
@@ -450,12 +376,41 @@ struct XAigerWriter
 						co_bits.emplace_back(b);
 						unused_bits.erase(I);
 					}
-				}
-
-				box_list.emplace_back(cell);
+				if (w->port_output)
+					for (const auto &b : rhs) {
+						SigBit O = sigmap(b);
+						if (O != b)
+							alias_map[O] = b;
+						ci_bits.emplace_back(b);
+						undriven_bits.erase(O);
+						// If PI and CI, then must be a (* keep *) wire
+						if (input_bits.erase(O)) {
+							log_assert(output_bits.count(O));
+							log_assert(O.wire->get_bool_attribute(ID::keep));
+						}
+					}
 			}
 
-			// TODO: Free memory from toposort, bit_drivers, bit_users
+			// Connect <cell>.abc9_ff.Q (inserted by abc9_map.v) as the last input to the flop box
+			if (box_module->get_bool_attribute("\\abc9_flop")) {
+				SigSpec rhs = module->wire(stringf("%s.abc9_ff.Q", cell->name.c_str()));
+				if (rhs.empty())
+					log_error("'%s.abc9_ff.Q' is not a wire present in module '%s'.\n", log_id(cell), log_id(module));
+
+				for (auto b : rhs) {
+					SigBit I = sigmap(b);
+					if (b == RTLIL::Sx)
+						b = State::S0;
+					else if (I != b) {
+						if (I == RTLIL::Sx)
+							alias_map[b] = State::S0;
+						else
+							alias_map[b] = I;
+					}
+					co_bits.emplace_back(b);
+					unused_bits.erase(I);
+				}
+			}
 		}
 
 		for (auto bit : input_bits)
@@ -501,6 +456,10 @@ struct XAigerWriter
 
 		for (auto &bit : ci_bits) {
 			aig_m++, aig_i++;
+			// 1'bx may exist here due to a box output
+			//   that has been padded to its full width
+			if (bit == State::Sx)
+				continue;
 			log_assert(!aig_map.count(bit));
 			aig_map[bit] = 2*aig_m;
 		}
@@ -512,7 +471,27 @@ struct XAigerWriter
 
 		for (const auto &bit : output_bits) {
 			ordered_outputs[bit] = aig_o++;
-			aig_outputs.push_back(bit2aig(bit));
+			int aig;
+			// Unlike bit2aig() which checks aig_map first, for
+			//   inout/keep bits, since aig_map will point to
+			//   the PI, first attempt to find the NOT/AND driver
+			//   before resorting to an aig_map lookup (which
+			//   could be another PO)
+			if (input_bits.count(bit)) {
+				if (not_map.count(bit)) {
+					aig = bit2aig(not_map.at(bit)) ^ 1;
+				} else if (and_map.count(bit)) {
+					auto args = and_map.at(bit);
+					int a0 = bit2aig(args.first);
+					int a1 = bit2aig(args.second);
+					aig = mkgate(a0, a1);
+				}
+				else
+					aig = aig_map.at(bit);
+			}
+			else
+				aig = bit2aig(bit);
+			aig_outputs.push_back(aig);
 		}
 
 		for (auto &i : ff_bits) {
@@ -612,106 +591,45 @@ struct XAigerWriter
 		//	write_o_buffer(0);
 
 		if (!box_list.empty() || !ff_bits.empty()) {
-			RTLIL::Module *holes_module = module->design->addModule("$__holes__");
-			log_assert(holes_module);
+			dict<IdString, std::tuple<int,int,int>> cell_cache;
 
-			dict<IdString, std::tuple<Cell*,int,int,int>> cell_cache;
-
-			int port_id = 1;
 			int box_count = 0;
 			for (auto cell : box_list) {
-				RTLIL::Module* orig_box_module = module->design->module(cell->type);
-				log_assert(orig_box_module);
-				IdString derived_name = orig_box_module->derive(module->design, cell->parameters);
-				RTLIL::Module* box_module = module->design->module(derived_name);
+				log_assert(cell);
 
-				auto r = cell_cache.insert(derived_name);
+				RTLIL::Module* box_module = module->design->module(cell->type);
+				log_assert(box_module);
+
+				IdString derived_type = box_module->derive(box_module->design, cell->parameters);
+				box_module = box_module->design->module(derived_type);
+				log_assert(box_module);
+
+				auto r = cell_cache.insert(derived_type);
 				auto &v = r.first->second;
 				if (r.second) {
-					if (box_module->has_processes())
-						Pass::call_on_module(module->design, box_module, "proc");
-
 					int box_inputs = 0, box_outputs = 0;
-					if (box_module->get_bool_attribute("\\whitebox")) {
-						auto holes_cell = holes_module->addCell(cell->name, derived_name);
-						for (auto port_name : box_ports.at(cell->type)) {
-							RTLIL::Wire *w = box_module->wire(port_name);
-							log_assert(w);
-							log_assert(!w->port_input || !w->port_output);
-							auto &conn = holes_cell->connections_[port_name];
-							if (w->port_input) {
-								for (int i = 0; i < GetSize(w); i++) {
-									box_inputs++;
-									RTLIL::Wire *holes_wire = holes_module->wire(stringf("\\i%d", box_inputs));
-									if (!holes_wire) {
-										holes_wire = holes_module->addWire(stringf("\\i%d", box_inputs));
-										holes_wire->port_input = true;
-										holes_wire->port_id = port_id++;
-										holes_module->ports.push_back(holes_wire->name);
-									}
-									conn.append(holes_wire);
-								}
-							}
-							else if (w->port_output) {
-								box_outputs += GetSize(w);
-								conn = holes_module->addWire(stringf("%s.%s", derived_name.c_str(), log_id(port_name)), GetSize(w));
-							}
-						}
-
-						// For flops only, create an extra 1-bit input that drives a new wire
-						//   called "<cell>.abc9_ff.Q" that is used below
-						if (box_module->get_bool_attribute("\\abc9_flop")) {
-							box_inputs++;
-							Wire *holes_wire = holes_module->wire(stringf("\\i%d", box_inputs));
-							if (!holes_wire) {
-								holes_wire = holes_module->addWire(stringf("\\i%d", box_inputs));
-								holes_wire->port_input = true;
-								holes_wire->port_id = port_id++;
-								holes_module->ports.push_back(holes_wire->name);
-							}
-							Wire *Q = holes_module->addWire(stringf("%s.abc9_ff.Q", cell->name.c_str()));
-							holes_module->connect(Q, holes_wire);
-						}
-
-						std::get<0>(v) = holes_cell;
-					}
-					else {
-						for (auto port_name : box_ports.at(cell->type)) {
-							RTLIL::Wire *w = box_module->wire(port_name);
-							log_assert(w);
-							log_assert(!w->port_input || !w->port_output);
-							if (w->port_input)
-								box_inputs += GetSize(w);
-							else if (w->port_output)
-								box_outputs += GetSize(w);
-						}
-						log_assert(std::get<0>(v) == nullptr);
+					for (auto port_name : box_module->ports) {
+						RTLIL::Wire *w = box_module->wire(port_name);
+						log_assert(w);
+						if (w->port_input)
+							box_inputs += GetSize(w);
+						if (w->port_output)
+							box_outputs += GetSize(w);
 					}
 
-					std::get<1>(v) = box_inputs;
-					std::get<2>(v) = box_outputs;
-					std::get<3>(v) = box_module->attributes.at("\\abc9_box_id").as_int();
-				}
-
-				auto holes_cell = std::get<0>(v);
-				for (auto port_name : box_ports.at(cell->type)) {
-					RTLIL::Wire *w = box_module->wire(port_name);
-					log_assert(w);
-					if (!w->port_output)
-						continue;
-					Wire *holes_wire = holes_module->addWire(stringf("$abc%s.%s", cell->name.c_str(), log_id(port_name)), GetSize(w));
-					holes_wire->port_output = true;
-					holes_wire->port_id = port_id++;
-					holes_module->ports.push_back(holes_wire->name);
-					if (holes_cell) // whitebox
-						holes_module->connect(holes_wire, holes_cell->getPort(port_name));
-					else // blackbox
-						holes_module->connect(holes_wire, Const(State::S0, GetSize(w)));
+					// For flops only, create an extra 1-bit input that drives a new wire
+					//   called "<cell>.abc9_ff.Q" that is used below
+					if (box_module->get_bool_attribute("\\abc9_flop"))
+						box_inputs++;
+
+					std::get<0>(v) = box_inputs;
+					std::get<1>(v) = box_outputs;
+					std::get<2>(v) = box_module->attributes.at("\\abc9_box_id").as_int();
 				}
 
+				write_h_buffer(std::get<0>(v));
 				write_h_buffer(std::get<1>(v));
 				write_h_buffer(std::get<2>(v));
-				write_h_buffer(std::get<3>(v));
 				write_h_buffer(box_count++);
 			}
 
@@ -759,82 +677,17 @@ struct XAigerWriter
 			f.write(reinterpret_cast<const char*>(&buffer_size_be), sizeof(buffer_size_be));
 			f.write(buffer_str.data(), buffer_str.size());
 
+			RTLIL::Module *holes_module = module->design->module(stringf("%s$holes", module->name.c_str()));
 			if (holes_module) {
-				log_push();
-
-				// NB: fixup_ports() will sort ports by name
-				//holes_module->fixup_ports();
-				holes_module->check();
-
-				// Cannot techmap/aigmap/check all lib_whitebox-es outside of write_xaiger
-				//   since boxes may contain parameters in which case `flatten` would have
-				//   created a new $paramod ...
-				Pass::call_on_module(holes_module->design, holes_module, "flatten -wb; techmap; aigmap");
-
-				SigMap holes_sigmap(holes_module);
-
-				dict<SigSpec, SigSpec> replace;
-				for (auto it = holes_module->cells_.begin(); it != holes_module->cells_.end(); ) {
-					auto cell = it->second;
-					if (cell->type.in("$_DFF_N_", "$_DFF_NN0_", "$_DFF_NN1_", "$_DFF_NP0_", "$_DFF_NP1_",
-								"$_DFF_P_", "$_DFF_PN0_", "$_DFF_PN1", "$_DFF_PP0_", "$_DFF_PP1_")) {
-						SigBit D = cell->getPort("\\D");
-						SigBit Q = cell->getPort("\\Q");
-						// Remove the $_DFF_* cell from what needs to be a combinatorial box
-						it = holes_module->cells_.erase(it);
-						Wire *port;
-						if (GetSize(Q.wire) == 1)
-							port = holes_module->wire(stringf("$abc%s", Q.wire->name.c_str()));
-						else
-							port = holes_module->wire(stringf("$abc%s[%d]", Q.wire->name.c_str(), Q.offset));
-						log_assert(port);
-						// Prepare to replace "assign <port> = $_DFF_*.Q;" with "assign <port> = $_DFF_*.D;"
-						//   in order to extract just the combinatorial control logic that feeds the box
-						//   (i.e. clock enable, synchronous reset, etc.)
-						replace.insert(std::make_pair(Q,D));
-						// Since `flatten` above would have created wires named "<cell>.Q",
-						//   extract the pre-techmap cell name
-						auto pos = Q.wire->name.str().rfind(".");
-						log_assert(pos != std::string::npos);
-						IdString driver = Q.wire->name.substr(0, pos);
-						// And drive the signal that was previously driven by "DFF.Q" (typically
-						//   used to implement clock-enable functionality) with the "<cell>.abc9_ff.Q"
-						//   wire (which itself is driven by an input port) we inserted above
-						Wire *currQ = holes_module->wire(stringf("%s.abc9_ff.Q", driver.c_str()));
-						log_assert(currQ);
-						holes_module->connect(Q, currQ);
-						continue;
-					}
-					else if (!cell->type.in("$_NOT_", "$_AND_"))
-						log_error("Whitebox contents cannot be represented as AIG. Please verify whiteboxes are synthesisable.\n");
-					++it;
-				}
-
-				for (auto &conn : holes_module->connections_) {
-					auto it = replace.find(sigmap(conn.second));
-					if (it != replace.end())
-						conn.second = it->second;
-				}
-
-				// Move into a new (temporary) design so that "clean" will only
-				// operate (and run checks on) this one module
-				RTLIL::Design *holes_design = new RTLIL::Design;
-				module->design->modules_.erase(holes_module->name);
-				holes_design->add(holes_module);
-				Pass::call(holes_design, "opt -purge");
-
 				std::stringstream a_buffer;
 				XAigerWriter writer(holes_module, true /* holes_mode */);
 				writer.write_aiger(a_buffer, false /*ascii_mode*/);
-				delete holes_design;
 
 				f << "a";
 				std::string buffer_str = a_buffer.str();
 				int32_t buffer_size_be = to_big_endian(buffer_str.size());
 				f.write(reinterpret_cast<const char*>(&buffer_size_be), sizeof(buffer_size_be));
 				f.write(buffer_str.data(), buffer_str.size());
-
-				log_pop();
 			}
 		}
 
@@ -917,7 +770,8 @@ struct XAigerBackend : public Backend {
 		log("Write the top module (according to the (* top *) attribute or if only one module\n");
 		log("is currently selected) to an XAIGER file. Any non $_NOT_, $_AND_, $_ABC9_FF_, or");
 		log("non (* abc9_box_id *) cells will be converted into psuedo-inputs and\n");
-		log("pseudo-outputs.\n");
+		log("pseudo-outputs. Whitebox contents will be taken from the '<module-name>$holes'\n");
+		log("module, if it exists.\n");
 		log("\n");
 		log("    -ascii\n");
 		log("        write ASCII version of AIGER format\n");
diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc
index 6735d670f..616b754ce 100644
--- a/backends/edif/edif.cc
+++ b/backends/edif/edif.cc
@@ -326,7 +326,7 @@ struct EdifBackend : public Backend {
 				continue;
 
 			SigMap sigmap(module);
-			std::map<RTLIL::SigSpec, std::set<std::string>> net_join_db;
+			std::map<RTLIL::SigSpec, std::set<std::pair<std::string, bool>>> net_join_db;
 
 			*f << stringf("    (cell %s\n", EDIF_DEF(module->name));
 			*f << stringf("      (cellType GENERIC)\n");
@@ -349,7 +349,7 @@ struct EdifBackend : public Backend {
 							add_prop(p.first, p.second);
 					*f << ")\n";
 					RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire));
-					net_join_db[sig].insert(stringf("(portRef %s)", EDIF_REF(wire->name)));
+					net_join_db[sig].insert(make_pair(stringf("(portRef %s)", EDIF_REF(wire->name)), wire->port_input));
 				} else {
 					int b[2];
 					b[wire->upto ? 0 : 1] = wire->start_offset;
@@ -362,7 +362,7 @@ struct EdifBackend : public Backend {
 					*f << ")\n";
 					for (int i = 0; i < wire->width; i++) {
 						RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire, i));
-						net_join_db[sig].insert(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), GetSize(wire)-i-1));
+						net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), GetSize(wire)-i-1), wire->port_input));
 					}
 				}
 			}
@@ -391,7 +391,7 @@ struct EdifBackend : public Backend {
 							log_warning("Bit %d of cell port %s.%s.%s driven by %s will be left unconnected in EDIF output.\n",
 									i, log_id(module), log_id(cell), log_id(p.first), log_signal(sig[i]));
 						else if (sig.size() == 1)
-							net_join_db[sig[i]].insert(stringf("(portRef %s (instanceRef %s))", EDIF_REF(p.first), EDIF_REF(cell->name)));
+							net_join_db[sig[i]].insert(make_pair(stringf("(portRef %s (instanceRef %s))", EDIF_REF(p.first), EDIF_REF(cell->name)), cell->output(p.first)));
 						else {
 							int member_idx = GetSize(sig)-i-1;
 							auto m = design->module(cell->type);
@@ -400,8 +400,8 @@ struct EdifBackend : public Backend {
 								if (w)
 									member_idx = GetSize(w)-i-1;
 							}
-							net_join_db[sig[i]].insert(stringf("(portRef (member %s %d) (instanceRef %s))",
-									EDIF_REF(p.first), member_idx, EDIF_REF(cell->name)));
+							net_join_db[sig[i]].insert(make_pair(stringf("(portRef (member %s %d) (instanceRef %s))",
+									EDIF_REF(p.first), member_idx, EDIF_REF(cell->name)), cell->output(p.first)));
 						}
 				}
 			}
@@ -410,11 +410,13 @@ struct EdifBackend : public Backend {
 				if (sig.wire == NULL && sig != RTLIL::State::S0 && sig != RTLIL::State::S1) {
 					if (sig == RTLIL::State::Sx) {
 						for (auto &ref : it.second)
-							log_warning("Exporting x-bit on %s as zero bit.\n", ref.c_str());
+							log_warning("Exporting x-bit on %s as zero bit.\n", ref.first.c_str());
 						sig = RTLIL::State::S0;
+					} else if (sig == RTLIL::State::Sz) {
+						continue;
 					} else {
 						for (auto &ref : it.second)
-							log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.c_str());
+							log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.first.c_str());
 						log_abort();
 					}
 				}
@@ -431,7 +433,7 @@ struct EdifBackend : public Backend {
 				}
 				*f << stringf("          (net %s (joined\n", EDIF_DEF(netname));
 				for (auto &ref : it.second)
-					*f << stringf("            %s\n", ref.c_str());
+					*f << stringf("            %s\n", ref.first.c_str());
 				if (sig.wire == NULL) {
 					if (nogndvcc)
 						log_error("Design contains constant nodes (map with \"hilomap\" first).\n");
@@ -446,6 +448,31 @@ struct EdifBackend : public Backend {
 						add_prop(p.first, p.second);
 				*f << stringf("\n          )\n");
 			}
+			for (auto &wire_it : module->wires_) {
+				RTLIL::Wire *wire = wire_it.second;
+				if (!wire->get_bool_attribute(ID::keep))
+					continue;
+				for(int i = 0; i < wire->width; i++) {
+					SigBit raw_sig = RTLIL::SigSpec(wire, i);
+					SigBit mapped_sig = sigmap(raw_sig);
+					if (raw_sig == mapped_sig || net_join_db.count(mapped_sig) == 0)
+						continue;
+					std::string netname = log_signal(raw_sig);
+					for (size_t i = 0; i < netname.size(); i++)
+						if (netname[i] == ' ' || netname[i] == '\\')
+							netname.erase(netname.begin() + i--);
+					*f << stringf("          (net %s (joined\n", EDIF_DEF(netname));
+					auto &refs = net_join_db.at(mapped_sig);
+					for (auto &ref : refs)
+						if (ref.second)
+							*f << stringf("            %s\n", ref.first.c_str());
+					*f << stringf("            )");
+					if (attr_properties && raw_sig.wire != NULL)
+						for (auto &p : raw_sig.wire->attributes)
+							add_prop(p.first, p.second);
+					*f << stringf("\n          )\n");
+				}
+			}
 			*f << stringf("        )\n");
 			*f << stringf("      )\n");
 			*f << stringf("    )\n");
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/backends/smt2/smtio.py b/backends/smt2/smtio.py
index 1df996aa7..34bf7ef38 100644
--- a/backends/smt2/smtio.py
+++ b/backends/smt2/smtio.py
@@ -304,7 +304,11 @@ class SmtIo:
 
     def p_open(self):
         assert self.p is None
-        self.p = subprocess.Popen(self.popen_vargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        try:
+            self.p = subprocess.Popen(self.popen_vargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        except FileNotFoundError:
+            print("%s SMT Solver '%s' not found in path." % (self.timestamp(), self.popen_vargs[0]), flush=True)
+            sys.exit(1)
         running_solvers[self.p_index] = self.p
         self.p_running = True
         self.p_next = None
diff --git a/frontends/aiger/aigerparse.cc b/frontends/aiger/aigerparse.cc
index a4b1e6fec..a42569301 100644
--- a/frontends/aiger/aigerparse.cc
+++ b/frontends/aiger/aigerparse.cc
@@ -393,21 +393,6 @@ void AigerReader::parse_xaiger()
 	if (f.peek() == '\n')
 		f.get();
 
-	dict<int,IdString> box_lookup;
-	for (auto m : design->modules()) {
-		auto it = m->attributes.find(ID(abc9_box_id));
-		if (it == m->attributes.end())
-			continue;
-		if (m->name.begins_with("$paramod"))
-			continue;
-		auto id = it->second.as_int();
-		auto r = box_lookup.insert(std::make_pair(id, m->name));
-		if (!r.second)
-			log_error("Module '%s' has the same abc9_box_id = %d value as '%s'.\n",
-					log_id(m), id, log_id(r.first->second));
-		log_assert(r.second);
-	}
-
 	// Parse footer (symbol table, comments, etc.)
 	std::string s;
 	for (int c = f.get(); c != EOF; c = f.get()) {
@@ -429,17 +414,23 @@ void AigerReader::parse_xaiger()
 				for (unsigned j = 0; j < cutLeavesM; ++j) {
 					nodeID = parse_xaiger_literal(f);
 					log_debug2("\t%u\n", nodeID);
+					if (nodeID == 0) {
+						log_debug("\tLUT '$lut$aiger%d$%d' input %d is constant!\n", aiger_autoidx, rootNodeID, cutLeavesM);
+						continue;
+					}
 					RTLIL::Wire *wire = module->wire(stringf("$aiger%d$%d", aiger_autoidx, nodeID));
 					log_assert(wire);
 					input_sig.append(wire);
 				}
+				// Reverse input order as fastest input is returned first
+				input_sig.reverse();
 				// TODO: Compute LUT mask from AIG in less than O(2 ** input_sig.size())
 				ce.clear();
 				ce.compute_deps(output_sig, input_sig.to_sigbit_pool());
-				RTLIL::Const lut_mask(RTLIL::State::Sx, 1 << input_sig.size());
-				for (int j = 0; j < (1 << cutLeavesM); ++j) {
+				RTLIL::Const lut_mask(RTLIL::State::Sx, 1 << GetSize(input_sig));
+				for (int j = 0; j < GetSize(lut_mask); ++j) {
 					int gray = j ^ (j >> 1);
-					ce.set_incremental(input_sig, RTLIL::Const{gray, static_cast<int>(cutLeavesM)});
+					ce.set_incremental(input_sig, RTLIL::Const{gray, GetSize(input_sig)});
 					RTLIL::SigBit o(output_sig);
 					bool success YS_ATTRIBUTE(unused) = ce.eval(o);
 					log_assert(success);
@@ -453,11 +444,13 @@ void AigerReader::parse_xaiger()
 			}
 		}
 		else if (c == 'r') {
-			uint32_t dataSize YS_ATTRIBUTE(unused) = parse_xaiger_literal(f);
+			uint32_t dataSize = parse_xaiger_literal(f);
 			flopNum = parse_xaiger_literal(f);
 			log_debug("flopNum = %u\n", flopNum);
 			log_assert(dataSize == (flopNum+1) * sizeof(uint32_t));
-			f.ignore(flopNum * sizeof(uint32_t));
+			mergeability.reserve(flopNum);
+			for (unsigned i = 0; i < flopNum; i++)
+				mergeability.emplace_back(parse_xaiger_literal(f));
 		}
 		else if (c == 'n') {
 			parse_xaiger_literal(f);
@@ -479,11 +472,15 @@ void AigerReader::parse_xaiger()
 			uint32_t boxNum = parse_xaiger_literal(f);
 			log_debug("boxNum = %u\n", boxNum);
 			for (unsigned i = 0; i < boxNum; i++) {
-				f.ignore(2*sizeof(uint32_t));
+				uint32_t boxInputs = parse_xaiger_literal(f);
+				uint32_t boxOutputs = parse_xaiger_literal(f);
 				uint32_t boxUniqueId = parse_xaiger_literal(f);
 				log_assert(boxUniqueId > 0);
 				uint32_t oldBoxNum = parse_xaiger_literal(f);
-				RTLIL::Cell* cell = module->addCell(stringf("$box%u", oldBoxNum), box_lookup.at(boxUniqueId));
+				RTLIL::Cell* cell = module->addCell(stringf("$box%u", oldBoxNum), stringf("$__boxid%u", boxUniqueId));
+				cell->setPort("\\i", SigSpec(State::S0, boxInputs));
+				cell->setPort("\\o", SigSpec(State::S0, boxOutputs));
+				cell->attributes["\\abc9_box_seq"] = oldBoxNum;
 				boxes.emplace_back(cell);
 			}
 		}
@@ -568,25 +565,18 @@ void AigerReader::parse_aiger_ascii()
 	}
 
 	// Parse outputs
+	digits = ceil(log10(O));
 	for (unsigned i = 0; i < O; ++i, ++line_count) {
 		if (!(f >> l1))
 			log_error("Line %u cannot be interpreted as an output!\n", line_count);
 
 		log_debug2("%d is an output\n", l1);
-		const unsigned variable = l1 >> 1;
-		const bool invert = l1 & 1;
-		RTLIL::IdString wire_name(stringf("$%d%s", variable, invert ? "b" : "")); // FIXME: is "b" the right suffix?
-		RTLIL::Wire *wire = module->wire(wire_name);
-		if (!wire)
-			wire = createWireIfNotExists(module, l1);
-		else if (wire->port_input || wire->port_output) {
-			RTLIL::Wire *new_wire = module->addWire(NEW_ID);
-			module->connect(new_wire, wire);
-			wire = new_wire;
-		}
+		RTLIL::Wire *wire = module->addWire(stringf("$o%0*d", digits, i));
 		wire->port_output = true;
+		module->connect(wire, createWireIfNotExists(module, l1));
 		outputs.push_back(wire);
 	}
+	//std::getline(f, line); // Ignore up to start of next line
 
 	// Parse bad properties
 	for (unsigned i = 0; i < B; ++i, ++line_count) {
@@ -598,6 +588,8 @@ void AigerReader::parse_aiger_ascii()
 		wire->port_output = true;
 		bad_properties.push_back(wire);
 	}
+	//if (B > 0)
+	//	std::getline(f, line); // Ignore up to start of next line
 
 	// TODO: Parse invariant constraints
 	for (unsigned i = 0; i < C; ++i, ++line_count)
@@ -753,84 +745,41 @@ void AigerReader::parse_aiger_binary()
 
 void AigerReader::post_process()
 {
-	dict<IdString, std::vector<IdString>> box_ports;
-	unsigned ci_count = 0, co_count = 0, flop_count = 0;
+	unsigned ci_count = 0, co_count = 0;
 	for (auto cell : boxes) {
-		RTLIL::Module* box_module = design->module(cell->type);
-		log_assert(box_module);
-
-		auto r = box_ports.insert(cell->type);
-		if (r.second) {
-			// Make carry in the last PI, and carry out the last PO
-			//   since ABC requires it this way
-			IdString carry_in, carry_out;
-			for (const auto &port_name : box_module->ports) {
-				auto w = box_module->wire(port_name);
-				log_assert(w);
-				if (w->get_bool_attribute("\\abc9_carry")) {
-					if (w->port_input)
-						carry_in = port_name;
-					if (w->port_output)
-						carry_out = port_name;
-				}
-				else
-					r.first->second.push_back(port_name);
-			}
-			if (carry_in != IdString()) {
-				log_assert(carry_out != IdString());
-				r.first->second.push_back(carry_in);
-				r.first->second.push_back(carry_out);
-			}
-		}
-
-		for (auto port_name : box_ports.at(cell->type)) {
-			RTLIL::Wire* port = box_module->wire(port_name);
-			log_assert(port);
-			RTLIL::SigSpec rhs;
-			for (int i = 0; i < GetSize(port); i++) {
-				RTLIL::Wire* wire = nullptr;
-				if (port->port_input) {
-					log_assert(co_count < outputs.size());
-					wire = outputs[co_count++];
-					log_assert(wire);
-					log_assert(wire->port_output);
-					wire->port_output = false;
-				}
-				if (port->port_output) {
-					log_assert((piNum + ci_count) < inputs.size());
-					wire = inputs[piNum + ci_count++];
-					log_assert(wire);
-					log_assert(wire->port_input);
-					wire->port_input = false;
-				}
-				rhs.append(wire);
-			}
-			cell->setPort(port_name, rhs);
-		}
-
-		if (box_module->attributes.count("\\abc9_flop")) {
+		for (auto &bit : cell->connections_.at("\\i")) {
+			log_assert(bit == State::S0);
 			log_assert(co_count < outputs.size());
-			Wire *wire = outputs[co_count++];
-			log_assert(wire);
-			log_assert(wire->port_output);
-			wire->port_output = false;
-
-			RTLIL::Wire *d = outputs[outputs.size() - flopNum + flop_count];
-			log_assert(d);
-			log_assert(d->port_output);
-			d->port_output = false;
-
-			RTLIL::Wire *q = inputs[piNum - flopNum + flop_count];
-			log_assert(q);
-			log_assert(q->port_input);
-			q->port_input = false;
-
-			auto ff = module->addCell(NEW_ID, "$__ABC9_FF_");
-			ff->setPort("\\D", d);
-			ff->setPort("\\Q", q);
-			flop_count++;
-			continue;
+			bit = outputs[co_count++];
+			log_assert(bit.wire && GetSize(bit.wire) == 1);
+			log_assert(bit.wire->port_output);
+			bit.wire->port_output = false;
 		}
+		for (auto &bit : cell->connections_.at("\\o")) {
+			log_assert(bit == State::S0);
+			log_assert((piNum + ci_count) < inputs.size());
+			bit = inputs[piNum + ci_count++];
+			log_assert(bit.wire && GetSize(bit.wire) == 1);
+			log_assert(bit.wire->port_input);
+			bit.wire->port_input = false;
+		}
+	}
+
+	for (uint32_t i = 0; i < flopNum; i++) {
+		RTLIL::Wire *d = outputs[outputs.size() - flopNum + i];
+		log_assert(d);
+		log_assert(d->port_output);
+		d->port_output = false;
+
+		RTLIL::Wire *q = inputs[piNum - flopNum + i];
+		log_assert(q);
+		log_assert(q->port_input);
+		q->port_input = false;
+
+		auto ff = module->addCell(NEW_ID, "$__ABC9_FF_");
+		ff->setPort("\\D", d);
+		ff->setPort("\\Q", q);
+		ff->attributes["\\abc9_mergeability"] = mergeability[i];
 	}
 
 	dict<RTLIL::IdString, int> wideports_cache;
@@ -859,6 +808,7 @@ void AigerReader::post_process()
 						wire->port_input = false;
 						module->connect(wire, existing);
 					}
+					log_debug(" -> %s\n", log_id(escaped_s));
 				}
 				else if (index > 0) {
 					std::string indexed_name = stringf("%s[%d]", escaped_s.c_str(), index);
@@ -872,18 +822,14 @@ void AigerReader::post_process()
 						module->connect(wire, existing);
 						wire->port_input = false;
 					}
+					log_debug(" -> %s\n", log_id(indexed_name));
 				}
-				log_debug(" -> %s\n", log_id(wire));
 			}
 			else if (type == "output") {
 				log_assert(static_cast<unsigned>(variable + co_count) < outputs.size());
 				RTLIL::Wire* wire = outputs[variable + co_count];
 				log_assert(wire);
 				log_assert(wire->port_output);
-				if (escaped_s == "$__dummy__") {
-					wire->port_output = false;
-					continue;
-				}
 				log_debug("Renaming output %s", log_id(wire));
 
 				if (index == 0) {
@@ -896,9 +842,11 @@ void AigerReader::post_process()
 					}
 					else {
 						wire->port_output = false;
+						existing->port_output = true;
 						module->connect(wire, existing);
 						wire = existing;
 					}
+					log_debug(" -> %s\n", log_id(escaped_s));
 				}
 				else if (index > 0) {
 					std::string indexed_name = stringf("%s[%d]", escaped_s.c_str(), index);
@@ -909,11 +857,12 @@ void AigerReader::post_process()
 							wideports_cache[escaped_s] = std::max(wideports_cache[escaped_s], index);
 					}
 					else {
-						module->connect(wire, existing);
 						wire->port_output = false;
+						existing->port_output = true;
+						module->connect(wire, existing);
 					}
+					log_debug(" -> %s\n", log_id(indexed_name));
 				}
-				log_debug(" -> %s\n", log_id(wire));
 				int init;
 				mf >> init;
 				if (init < 2)
@@ -921,26 +870,8 @@ void AigerReader::post_process()
 			}
 			else if (type == "box") {
 				RTLIL::Cell* cell = module->cell(stringf("$box%d", variable));
-				if (cell) { // ABC could have optimised this box away
+				if (cell) // ABC could have optimised this box away
 					module->rename(cell, escaped_s);
-					for (const auto &i : cell->connections()) {
-						RTLIL::IdString port_name = i.first;
-						RTLIL::SigSpec rhs = i.second;
-						int index = 0;
-						for (auto bit : rhs.bits()) {
-							RTLIL::Wire* wire = bit.wire;
-							RTLIL::IdString escaped_s = RTLIL::escape_id(stringf("%s.%s", log_id(cell), log_id(port_name)));
-							if (index == 0)
-								module->rename(wire, escaped_s);
-							else if (index > 0) {
-								module->rename(wire, stringf("%s[%d]", escaped_s.c_str(), index));
-								if (wideports)
-									wideports_cache[escaped_s] = std::max(wideports_cache[escaped_s], index);
-							}
-							index++;
-						}
-					}
-				}
 			}
 			else
 				log_error("Symbol type '%s' not recognised.\n", type.c_str());
@@ -1018,18 +949,21 @@ struct AigerFrontend : public Frontend {
 		log("Load module from an AIGER file into the current design.\n");
 		log("\n");
 		log("    -module_name <module_name>\n");
-		log("        Name of module to be created (default: <filename>)\n");
+		log("        name of module to be created (default: <filename>)\n");
 		log("\n");
 		log("    -clk_name <wire_name>\n");
-		log("        If specified, AIGER latches to be transformed into $_DFF_P_ cells\n");
-		log("        clocked by wire of this name. Otherwise, $_FF_ cells will be used.\n");
+		log("        if specified, AIGER latches to be transformed into $_DFF_P_ cells\n");
+		log("        clocked by wire of this name. otherwise, $_FF_ cells will be used\n");
 		log("\n");
 		log("    -map <filename>\n");
 		log("        read file with port and latch symbols\n");
 		log("\n");
 		log("    -wideports\n");
-		log("        Merge ports that match the pattern 'name[int]' into a single\n");
-		log("        multi-bit port 'name'.\n");
+		log("        merge ports that match the pattern 'name[int]' into a single\n");
+		log("        multi-bit port 'name'\n");
+		log("\n");
+		log("    -xaiger\n");
+		log("        read XAIGER extensions\n");
 		log("\n");
 	}
 	void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
@@ -1039,7 +973,7 @@ struct AigerFrontend : public Frontend {
 		RTLIL::IdString clk_name;
 		RTLIL::IdString module_name;
 		std::string map_filename;
-		bool wideports = false;
+		bool wideports = false, xaiger = false;
 
 		size_t argidx;
 		for (argidx = 1; argidx < args.size(); argidx++) {
@@ -1060,6 +994,10 @@ struct AigerFrontend : public Frontend {
 				wideports = true;
 				continue;
 			}
+			if (arg == "-xaiger") {
+				xaiger = true;
+				continue;
+			}
 			break;
 		}
 		extra_args(f, filename, args, argidx, true);
@@ -1079,7 +1017,10 @@ struct AigerFrontend : public Frontend {
 		}
 
 		AigerReader reader(design, *f, module_name, clk_name, map_filename, wideports);
-		reader.parse_aiger();
+		if (xaiger)
+			reader.parse_xaiger();
+		else
+			reader.parse_aiger();
 	}
 } AigerFrontend;
 
diff --git a/frontends/aiger/aigerparse.h b/frontends/aiger/aigerparse.h
index 722f1e472..46ac81212 100644
--- a/frontends/aiger/aigerparse.h
+++ b/frontends/aiger/aigerparse.h
@@ -45,6 +45,7 @@ struct AigerReader
     std::vector<RTLIL::Wire*> outputs;
     std::vector<RTLIL::Wire*> bad_properties;
     std::vector<RTLIL::Cell*> boxes;
+    std::vector<int> mergeability;
 
     AigerReader(RTLIL::Design *design, std::istream &f, RTLIL::IdString module_name, RTLIL::IdString clk_name, std::string map_filename, bool wideports);
     void parse_aiger();
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 b94a8d710..8855d9954 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/verific/verific.cc b/frontends/verific/verific.cc
index 9274cf5ca..ae5815f8e 100644
--- a/frontends/verific/verific.cc
+++ b/frontends/verific/verific.cc
@@ -539,6 +539,14 @@ bool VerificImporter::import_netlist_instance_cells(Instance *inst, RTLIL::IdStr
 		return true;
 	}
 
+	if (inst->Type() == OPER_REDUCE_NAND) {
+		Wire *tmp = module->addWire(NEW_ID);
+		cell = module->addReduceAnd(inst_name, IN, tmp, SIGNED);
+		module->addNot(NEW_ID, tmp, net_map_at(inst->GetOutput()));
+		import_attributes(cell->attributes, inst);
+		return true;
+	}
+
 	if (inst->Type() == OPER_REDUCE_OR) {
 		cell = module->addReduceOr(inst_name, IN, net_map_at(inst->GetOutput()), SIGNED);
 		import_attributes(cell->attributes, inst);
@@ -1891,6 +1899,9 @@ void verific_import(Design *design, const std::map<std::string,std::string> &par
 	if (!verific_error_msg.empty())
 		log_error("%s\n", verific_error_msg.c_str());
 
+	for (auto nl : nl_todo)
+	    nl->ChangePortBusStructures(1 /* hierarchical */);
+
 	VerificExtNets worker;
 	for (auto nl : nl_todo)
 		worker.run(nl);
@@ -2408,7 +2419,7 @@ struct VerificPass : public Pass {
 			else
 			{
 				if (argidx == GetSize(args))
-					log_cmd_error("No top module specified.\n");
+					cmd_error(args, argidx, "No top module specified.\n");
 
 				Array veri_modules, vhdl_units;
 				for (; argidx < GetSize(args); argidx++)
@@ -2470,6 +2481,9 @@ struct VerificPass : public Pass {
 					worker.run(nl);
 			}
 
+			for (auto nl : nl_todo)
+				nl->ChangePortBusStructures(1 /* hierarchical */);
+
 			if (!dumpfile.empty()) {
 				VeriWrite veri_writer;
 				veri_writer.WriteFile(dumpfile.c_str(), Netlist::PresentDesign());
@@ -2495,7 +2509,7 @@ struct VerificPass : public Pass {
 			goto check_error;
 		}
 
-		log_cmd_error("Missing or unsupported mode parameter.\n");
+		cmd_error(args, argidx, "Missing or unsupported mode parameter.\n");
 
 	check_error:
 		if (!verific_error_msg.empty())
@@ -2568,14 +2582,14 @@ struct ReadPass : public Pass {
 		static bool use_verific = verific_available;
 
 		if (args.size() < 2 || args[1][0] != '-')
-			log_cmd_error("Missing mode parameter.\n");
+			cmd_error(args, 1, "Missing mode parameter.\n");
 
 		if (args[1] == "-verific" || args[1] == "-noverific") {
 			if (args.size() != 2)
-				log_cmd_error("Additional arguments to -verific/-noverific.\n");
+				cmd_error(args, 1, "Additional arguments to -verific/-noverific.\n");
 			if (args[1] == "-verific") {
 				if (!verific_available)
-					log_cmd_error("This version of Yosys is built without Verific support.\n");
+					cmd_error(args, 1, "This version of Yosys is built without Verific support.\n");
 				use_verific = true;
 			} else {
 				use_verific = false;
@@ -2584,7 +2598,7 @@ struct ReadPass : public Pass {
 		}
 
 		if (args.size() < 3)
-			log_cmd_error("Missing file name parameter.\n");
+			cmd_error(args, 3, "Missing file name parameter.\n");
 
 		if (args[1] == "-vlog95" || args[1] == "-vlog2k") {
 			if (use_verific) {
@@ -2616,7 +2630,7 @@ struct ReadPass : public Pass {
 				args[0] = "verific";
 				Pass::call(design, args);
 			} else {
-				log_cmd_error("This version of Yosys is built without Verific support.\n");
+				cmd_error(args, 1, "This version of Yosys is built without Verific support.\n");
 			}
 			return;
 		}
@@ -2663,7 +2677,7 @@ struct ReadPass : public Pass {
 			return;
 		}
 
-		log_cmd_error("Missing or unsupported mode parameter.\n");
+		cmd_error(args, 1, "Missing or unsupported mode parameter.\n");
 	}
 } ReadPass;
 
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/kernel/driver.cc b/kernel/driver.cc
index f0d495b46..9040408bc 100644
--- a/kernel/driver.cc
+++ b/kernel/driver.cc
@@ -295,6 +295,9 @@ int main(int argc, char **argv)
 		printf("    -E <depsfile>\n");
 		printf("        write a Makefile dependencies file with in- and output file names\n");
 		printf("\n");
+		printf("    -x <feature>\n");
+		printf("        do not print warnings for the specified experimental feature\n");
+		printf("\n");
 		printf("    -g\n");
 		printf("        globally enable debug log messages\n");
 		printf("\n");
@@ -317,8 +320,14 @@ int main(int argc, char **argv)
 		exit(0);
 	}
 
+	if (argc == 2 && (!strcmp(argv[1], "-V") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version")))
+	{
+		printf("%s\n", yosys_version_str);
+		exit(0);
+	}
+
 	int opt;
-	while ((opt = getopt(argc, argv, "MXAQTVSgm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:D:P:E:")) != -1)
+	while ((opt = getopt(argc, argv, "MXAQTVSgm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:D:P:E:x:")) != -1)
 	{
 		switch (opt)
 		{
@@ -449,6 +458,9 @@ int main(int argc, char **argv)
 		case 'E':
 			depsfile = optarg;
 			break;
+		case 'x':
+			log_experimentals_ignored.insert(optarg);
+			break;
 		default:
 			fprintf(stderr, "Run '%s -h' for help.\n", argv[0]);
 			exit(1);
@@ -561,6 +573,10 @@ int main(int argc, char **argv)
 
 		if (log_warnings_count)
 			log("Warnings: %d unique messages, %d total\n", GetSize(log_warnings), log_warnings_count);
+
+		if (!log_experimentals.empty())
+			log("Warnings: %d experimental features used (not excluded with -x).\n", GetSize(log_experimentals));
+
 #ifdef _WIN32
 		log("End of script. Logfile hash: %s\n", hash.c_str());
 #else
diff --git a/kernel/log.cc b/kernel/log.cc
index c5ba0d10d..f5d6c488e 100644
--- a/kernel/log.cc
+++ b/kernel/log.cc
@@ -42,7 +42,7 @@ std::vector<FILE*> log_files;
 std::vector<std::ostream*> log_streams;
 std::map<std::string, std::set<std::string>> log_hdump;
 std::vector<std::regex> log_warn_regexes, log_nowarn_regexes, log_werror_regexes;
-std::set<std::string> log_warnings;
+std::set<std::string> log_warnings, log_experimentals, log_experimentals_ignored;
 int log_warnings_count = 0;
 bool log_hdump_all = false;
 FILE *log_errfile = NULL;
@@ -377,6 +377,19 @@ void log_warning(const char *format, ...)
 	va_end(ap);
 }
 
+void log_experimental(const char *format, ...)
+{
+	va_list ap;
+	va_start(ap, format);
+	string s = vstringf(format, ap);
+	va_end(ap);
+
+	if (log_experimentals_ignored.count(s) == 0 && log_experimentals.count(s) == 0) {
+		log_warning("Feature '%s' is experimental.\n", s.c_str());
+		log_experimentals.insert(s);
+	}
+}
+
 void log_warning_noprefix(const char *format, ...)
 {
 	va_list ap;
diff --git a/kernel/log.h b/kernel/log.h
index 1f15f3459..9db8efaa5 100644
--- a/kernel/log.h
+++ b/kernel/log.h
@@ -50,7 +50,7 @@ extern std::vector<FILE*> log_files;
 extern std::vector<std::ostream*> log_streams;
 extern std::map<std::string, std::set<std::string>> log_hdump;
 extern std::vector<std::regex> log_warn_regexes, log_nowarn_regexes, log_werror_regexes;
-extern std::set<std::string> log_warnings;
+extern std::set<std::string> log_warnings, log_experimentals, log_experimentals_ignored;
 extern int log_warnings_count;
 extern bool log_hdump_all;
 extern FILE *log_errfile;
@@ -77,6 +77,7 @@ YS_NORETURN void logv_error(const char *format, va_list ap) YS_ATTRIBUTE(noretur
 void log(const char *format, ...)  YS_ATTRIBUTE(format(printf, 1, 2));
 void log_header(RTLIL::Design *design, const char *format, ...) YS_ATTRIBUTE(format(printf, 2, 3));
 void log_warning(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2));
+void log_experimental(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2));
 
 // Log with filename to report a problem in a source file.
 void log_file_warning(const std::string &filename, int lineno, const char *format, ...) YS_ATTRIBUTE(format(printf, 3, 4));
diff --git a/kernel/register.cc b/kernel/register.cc
index 5d0fb3c8c..e59d59654 100644
--- a/kernel/register.cc
+++ b/kernel/register.cc
@@ -304,6 +304,9 @@ void Pass::call(RTLIL::Design *design, std::vector<std::string> args)
 	if (pass_register.count(args[0]) == 0)
 		log_cmd_error("No such command: %s (type 'help' for a command overview)\n", args[0].c_str());
 
+	if (pass_register[args[0]]->experimental_flag)
+		log_experimental("%s", args[0].c_str());
+
 	size_t orig_sel_stack_pos = design->selection_stack.size();
 	auto state = pass_register[args[0]]->pre_execute();
 	pass_register[args[0]]->execute(args, design);
@@ -824,6 +827,11 @@ struct HelpPass : public Pass {
 						log("=");
 					log("\n");
 					it.second->help();
+					if (it.second->experimental_flag) {
+						log("\n");
+						log("WARNING: THE '%s' COMMAND IS EXPERIMENTAL.\n", it.first.c_str());
+						log("\n");
+					}
 				}
 			}
 			else if (args[1] == "-cells") {
@@ -846,6 +854,11 @@ struct HelpPass : public Pass {
 					std::ostringstream buf;
 					log_streams.push_back(&buf);
 					it.second->help();
+					if (it.second->experimental_flag) {
+						log("\n");
+						log("WARNING: THE '%s' COMMAND IS EXPERIMENTAL.\n", it.first.c_str());
+						log("\n");
+					}
 					log_streams.pop_back();
 					write_tex(f, it.first, it.second->short_help, buf.str());
 				}
@@ -858,6 +871,11 @@ struct HelpPass : public Pass {
 					std::ostringstream buf;
 					log_streams.push_back(&buf);
 					it.second->help();
+					if (it.second->experimental_flag) {
+						log("\n");
+						log("WARNING: THE '%s' COMMAND IS EXPERIMENTAL.\n", it.first.c_str());
+						log("\n");
+					}
 					log_streams.pop_back();
 					write_html(f, it.first, it.second->short_help, buf.str());
 				}
@@ -865,6 +883,11 @@ struct HelpPass : public Pass {
 			}
 			else if (pass_register.count(args[1])) {
 				pass_register.at(args[1])->help();
+				if (pass_register.at(args[1])->experimental_flag) {
+					log("\n");
+					log("WARNING: THE '%s' COMMAND IS EXPERIMENTAL.\n", args[1].c_str());
+					log("\n");
+				}
 			}
 			else if (cell_help_messages.cell_help.count(args[1])) {
 				log("%s", cell_help_messages.cell_help.at(args[1]).c_str());
diff --git a/kernel/register.h b/kernel/register.h
index 821faff3e..4622845b6 100644
--- a/kernel/register.h
+++ b/kernel/register.h
@@ -36,6 +36,11 @@ struct Pass
 
 	int call_counter;
 	int64_t runtime_ns;
+	bool experimental_flag = false;
+
+	void experimental() {
+		experimental_flag = true;
+	}
 
 	struct pre_post_exec_state_t {
 		Pass *parent_pass;
diff --git a/kernel/rtlil.h b/kernel/rtlil.h
index 6251d265d..58c5d9674 100644
--- a/kernel/rtlil.h
+++ b/kernel/rtlil.h
@@ -851,6 +851,8 @@ public:
 
 	RTLIL::SigSpec repeat(int num) const;
 
+	void reverse() { inline_unpack(); std::reverse(bits_.begin(), bits_.end()); }
+
 	bool operator <(const RTLIL::SigSpec &other) const;
 	bool operator ==(const RTLIL::SigSpec &other) const;
 	inline bool operator !=(const RTLIL::SigSpec &other) const { return !(*this == other); }
diff --git a/manual/CHAPTER_CellLib.tex b/manual/CHAPTER_CellLib.tex
index 00a88cc82..55abd9b96 100644
--- a/manual/CHAPTER_CellLib.tex
+++ b/manual/CHAPTER_CellLib.tex
@@ -198,8 +198,8 @@ calculated signal and a constant zero with an {\tt \$and} gate).
 
 \subsection{Registers}
 
-D-Type Flip-Flops are represented by {\tt \$dff} cells. These cells have a clock port \B{CLK},
-an input port \B{D} and an output port \B{Q}. The following parameters are available for \$dff
+D-type flip-flops are represented by {\tt \$dff} cells. These cells have a clock port \B{CLK},
+an input port \B{D} and an output port \B{Q}. The following parameters are available for {\tt \$dff}
 cells:
 
 \begin{itemize}
@@ -211,13 +211,23 @@ Clock is active on the positive edge if this parameter has the value {\tt 1'b1}
 edge if this parameter is {\tt 1'b0}.
 \end{itemize}
 
-D-Type Flip-Flops with asynchronous resets are represented by {\tt \$adff} cells. As the {\tt \$dff}
+D-type flip-flops with enable are represented by {\tt \$dffe} cells. As the {\tt \$dff}
+cells they have \B{CLK}, \B{D} and \B{Q} ports. In addition they also have a single-bit \B{EN}
+input port for the enable pin and the following parameter:
+
+\begin{itemize}
+\item \B{EN\_POLARITY} \\
+The enable input is active-high if this parameter has the value {\tt 1'b1} and active-low
+if this parameter is {\tt 1'b0}.
+\end{itemize}
+
+D-type flip-flops with asynchronous reset are represented by {\tt \$adff} cells. As the {\tt \$dff}
 cells they have \B{CLK}, \B{D} and \B{Q} ports. In addition they also have a single-bit \B{ARST}
 input port for the reset pin and the following additional two parameters:
 
 \begin{itemize}
 \item \B{ARST\_POLARITY} \\
-The asynchronous reset is high-active if this parameter has the value {\tt 1'b1} and low-active
+The asynchronous reset is active-high if this parameter has the value {\tt 1'b1} and active-low
 if this parameter is {\tt 1'b0}.
 
 \item \B{ARST\_VALUE} \\
@@ -231,8 +241,27 @@ Usually these cells are generated by the {\tt proc} pass using the information
 in the designs RTLIL::Process objects.
 \end{sloppypar}
 
+D-type flip-flops with asynchronous set and reset are represented by {\tt \$dffsr} cells.
+As the {\tt \$dff} cells they have \B{CLK}, \B{D} and \B{Q} ports. In addition they also have
+a single-bit \B{SET} input port for the set pin, a single-bit \B{CLR} input port for the reset pin,
+and the following two parameters:
+
+\begin{itemize}
+\item \B{SET\_POLARITY} \\
+The set input is active-high if this parameter has the value {\tt 1'b1} and active-low
+if this parameter is {\tt 1'b0}.
+
+\item \B{CLR\_POLARITY} \\
+The reset input is active-high if this parameter has the value {\tt 1'b1} and active-low
+if this parameter is {\tt 1'b0}.
+\end{itemize}
+
+When both the set and reset inputs of a {\tt \$dffsr} cell are active, the reset input takes
+precedence.
+
 \begin{fixme}
-Add information about {\tt \$sr} cells (set-reset flip-flops) and d-type latches.
+Add information about {\tt \$sr} cells (set-reset flip-flops), {\tt \$dlatch} cells (d-type latches),
+and {\tt \$dlatchsr} cells (d-type latches with set/reset).
 \end{fixme}
 
 \subsection{Memories}
@@ -451,6 +480,30 @@ $ClkEdge$ & $RstLvl$ & $RstVal$ & Cell Type \\
 \lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];0; & {\tt \$\_DFF\_PP0\_} \\
 \lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];1; & {\tt \$\_DFF\_PP1\_} \\
 \end{tabular}
+% FIXME: the layout of this is broken and I have no idea how to fix it
+\hfil
+\begin{tabular}[t]{lll}
+$ClkEdge$ & $EnLvl$ & Cell Type \\
+\hline
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];0; & {\tt \$\_DFFE\_NN\_} \\
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];1; & {\tt \$\_DFFE\_NP\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];0; & {\tt \$\_DFFE\_PN\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];1; & {\tt \$\_DFFE\_PP\_} \\
+\end{tabular}
+% FIXME: the layout of this is broken too
+\hfil
+\begin{tabular}[t]{llll}
+$ClkEdge$ & $SetLvl$ & $RstLvl$ & Cell Type \\
+\hline
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];0; & \lstinline[language=Verilog];0; & {\tt \$\_DFFSR\_NNN\_} \\
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];0; & \lstinline[language=Verilog];1; & {\tt \$\_DFFSR\_NNP\_} \\
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];0; & {\tt \$\_DFFSR\_NPN\_} \\
+\lstinline[language=Verilog];negedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];1; & {\tt \$\_DFFSR\_NPP\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];0; & \lstinline[language=Verilog];0; & {\tt \$\_DFFSR\_PNN\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];0; & \lstinline[language=Verilog];1; & {\tt \$\_DFFSR\_PNP\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];0; & {\tt \$\_DFFSR\_PPN\_} \\
+\lstinline[language=Verilog];posedge; & \lstinline[language=Verilog];1; & \lstinline[language=Verilog];1; & {\tt \$\_DFFSR\_PPP\_} \\
+\end{tabular}
 \caption{Cell types for gate level logic networks}
 \label{tab:CellLib_gates}
 \end{table}
@@ -459,11 +512,22 @@ Table~\ref{tab:CellLib_gates} lists all cell types used for gate level logic. Th
 {\tt \$\_NOT\_}, {\tt \$\_AND\_}, {\tt \$\_NAND\_}, {\tt \$\_ANDNOT\_}, {\tt \$\_OR\_}, {\tt \$\_NOR\_},
 {\tt \$\_ORNOT\_}, {\tt \$\_XOR\_}, {\tt \$\_XNOR\_} and {\tt \$\_MUX\_} are used to model combinatorial logic.
 The cell type {\tt \$\_TBUF\_} is used to model tristate logic.
+
 The cell types {\tt \$\_DFF\_N\_} and {\tt \$\_DFF\_P\_} represent d-type flip-flops.
 
+The cell types {\tt \$\_DFFE\_NN\_}, {\tt \$\_DFFE\_NP\_}, {\tt \$\_DFFE\_PN\_} and {\tt \$\_DFFE\_PP\_}
+implement d-type flip-flops with enable. The values in the table for these cell types relate to the
+following Verilog code template.
+
+\begin{lstlisting}[mathescape,language=Verilog]
+	always @($ClkEdge$ C)
+		if (EN == $EnLvl$)
+			Q <= D;
+\end{lstlisting}
+
 The cell types {\tt \$\_DFF\_NN0\_}, {\tt \$\_DFF\_NN1\_}, {\tt \$\_DFF\_NP0\_}, {\tt \$\_DFF\_NP1\_},
 {\tt \$\_DFF\_PN0\_}, {\tt \$\_DFF\_PN1\_}, {\tt \$\_DFF\_PP0\_} and {\tt \$\_DFF\_PP1\_} implement
-d-type flip-flops with asynchronous resets. The values in the table for these cell types relate to the
+d-type flip-flops with asynchronous reset. The values in the table for these cell types relate to the
 following Verilog code template, where \lstinline[mathescape,language=Verilog];$RstEdge$; is \lstinline[language=Verilog];posedge;
 if \lstinline[mathescape,language=Verilog];$RstLvl$; if \lstinline[language=Verilog];1;, and \lstinline[language=Verilog];negedge;
 otherwise.
@@ -476,6 +540,25 @@ otherwise.
 			Q <= D;
 \end{lstlisting}
 
+The cell types {\tt \$\_DFFSR\_NNN\_}, {\tt \$\_DFFSR\_NNP\_}, {\tt \$\_DFFSR\_NPN\_}, {\tt \$\_DFFSR\_NPP\_},
+{\tt \$\_DFFSR\_PNN\_}, {\tt \$\_DFFSR\_PNP\_}, {\tt \$\_DFFSR\_PPN\_} and {\tt \$\_DFFSR\_PPP\_} implement
+d-type flip-flops with asynchronous set and reset. The values in the table for these cell types relate to the
+following Verilog code template, where \lstinline[mathescape,language=Verilog];$RstEdge$; is \lstinline[language=Verilog];posedge;
+if \lstinline[mathescape,language=Verilog];$RstLvl$; if \lstinline[language=Verilog];1;, \lstinline[language=Verilog];negedge;
+otherwise, and \lstinline[mathescape,language=Verilog];$SetEdge$; is \lstinline[language=Verilog];posedge;
+if \lstinline[mathescape,language=Verilog];$SetLvl$; if \lstinline[language=Verilog];1;, \lstinline[language=Verilog];negedge;
+otherwise.
+
+\begin{lstlisting}[mathescape,language=Verilog]
+	always @($ClkEdge$ C, $RstEdge$ R, $SetEdge$ S)
+		if (R == $RstLvl$)
+			Q <= 0;
+		else if (S == $SetLvl$)
+			Q <= 1;
+		else
+			Q <= D;
+\end{lstlisting}
+
 In most cases gate level logic networks are created from RTL networks using the {\tt techmap} pass. The flip-flop cells
 from the gate level logic network can be mapped to physical flip-flop cells from a Liberty file using the {\tt dfflibmap}
 pass. The combinatorial logic cells can be mapped to physical cells from a Liberty file via ABC \citeweblink{ABC}
@@ -507,11 +590,7 @@ Add information about {\tt \$ff} and {\tt \$\_FF\_} cells.
 \end{fixme}
 
 \begin{fixme}
-Add information about {\tt \$dffe}, {\tt \$dffsr}, {\tt \$dlatch}, and {\tt \$dlatchsr} cells.
-\end{fixme}
-
-\begin{fixme}
-Add information about {\tt \$\_DFFE\_??\_}, {\tt \$\_DFFSR\_???\_}, {\tt \$\_DLATCH\_?\_}, and {\tt \$\_DLATCHSR\_???\_} cells.
+Add information about {\tt \$\_DLATCH\_?\_}, and {\tt \$\_DLATCHSR\_???\_} cells.
 \end{fixme}
 
 \begin{fixme}
diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py
index c58c3f66a..9b4e644c0 100644
--- a/misc/py_wrap_generator.py
+++ b/misc/py_wrap_generator.py
@@ -1935,6 +1935,19 @@ def parse_header(source):
 		line = source_text[i].replace("YOSYS_NAMESPACE_BEGIN", "                    namespace YOSYS_NAMESPACE{").replace("YOSYS_NAMESPACE_END","                    }")
 		ugly_line = unpretty_string(line)
 
+		# for anonymous unions, ignore union enclosure by skipping start line and replacing end line with new line
+		if 'union {' in line:
+			j = i+1
+			while j < len(source_text):
+				union_line = source_text[j]
+				if '};' in union_line:
+					source_text[j] = '\n'
+					break
+				j += 1
+			if j != len(source_text):
+				i += 1
+				continue
+
 		if str.startswith(ugly_line, "namespace "):# and ugly_line.find("std") == -1 and ugly_line.find("__") == -1:
 			namespace_name = ugly_line[10:].replace("{","").strip()
 			namespaces.append((namespace_name, ugly_line.count("{")))
diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc
index a3e969ef1..eeef24bde 100644
--- a/passes/cmds/show.cc
+++ b/passes/cmds/show.cc
@@ -873,7 +873,7 @@ struct ShowPass : public Pass {
 			#ifdef __APPLE__
 			std::string cmd = stringf("ps -fu %d | grep -q '[ ]%s' || xdot '%s' &", getuid(), dot_file.c_str(), dot_file.c_str());
 			#else
-			std::string cmd = stringf("{ test -f '%s.pid' && fuser -s '%s.pid'; } || ( echo $$ >&3; exec xdot '%s'; ) 3> '%s.pid' &", dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), dot_file.c_str());
+			std::string cmd = stringf("{ test -f '%s.pid' && fuser -s '%s.pid' 2> /dev/null; } || ( echo $$ >&3; exec xdot '%s'; ) 3> '%s.pid' &", dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), dot_file.c_str());
 			#endif
 			log("Exec: %s\n", cmd.c_str());
 			if (run_command(cmd) != 0)
diff --git a/passes/fsm/fsm_detect.cc b/passes/fsm/fsm_detect.cc
index fb3896669..a1c8067b4 100644
--- a/passes/fsm/fsm_detect.cc
+++ b/passes/fsm/fsm_detect.cc
@@ -34,13 +34,20 @@ static SigSet<sig2driver_entry_t> sig2driver, sig2user;
 static std::set<RTLIL::Cell*> muxtree_cells;
 static SigPool sig_at_port;
 
-static bool check_state_mux_tree(RTLIL::SigSpec old_sig, RTLIL::SigSpec sig, pool<Cell*> &recursion_monitor)
+static bool check_state_mux_tree(RTLIL::SigSpec old_sig, RTLIL::SigSpec sig, pool<Cell*> &recursion_monitor, dict<RTLIL::SigSpec, bool> &mux_tree_cache)
 {
+	if (mux_tree_cache.find(sig) != mux_tree_cache.end())
+		return mux_tree_cache.at(sig);
+
 	if (sig.is_fully_const() || old_sig == sig) {
+ret_true:
+		mux_tree_cache[sig] = true;
 		return true;
 	}
 
 	if (sig_at_port.check_any(assign_map(sig))) {
+ret_false:
+		mux_tree_cache[sig] = false;
 		return false;
 	}
 
@@ -49,13 +56,13 @@ static bool check_state_mux_tree(RTLIL::SigSpec old_sig, RTLIL::SigSpec sig, poo
 	for (auto &cellport : cellport_list)
 	{
 		if ((cellport.first->type != "$mux" && cellport.first->type != "$pmux") || cellport.second != "\\Y") {
-			return false;
+			goto ret_false;
 		}
 
 		if (recursion_monitor.count(cellport.first)) {
 			log_warning("logic loop in mux tree at signal %s in module %s.\n",
 					log_signal(sig), RTLIL::id2cstr(module->name));
-			return false;
+			goto ret_false;
 		}
 
 		recursion_monitor.insert(cellport.first);
@@ -63,22 +70,22 @@ static bool check_state_mux_tree(RTLIL::SigSpec old_sig, RTLIL::SigSpec sig, poo
 		RTLIL::SigSpec sig_a = assign_map(cellport.first->getPort("\\A"));
 		RTLIL::SigSpec sig_b = assign_map(cellport.first->getPort("\\B"));
 
-		if (!check_state_mux_tree(old_sig, sig_a, recursion_monitor)) {
+		if (!check_state_mux_tree(old_sig, sig_a, recursion_monitor, mux_tree_cache)) {
 			recursion_monitor.erase(cellport.first);
-			return false;
+			goto ret_false;
 		}
 
 		for (int i = 0; i < sig_b.size(); i += sig_a.size())
-			if (!check_state_mux_tree(old_sig, sig_b.extract(i, sig_a.size()), recursion_monitor)) {
+			if (!check_state_mux_tree(old_sig, sig_b.extract(i, sig_a.size()), recursion_monitor, mux_tree_cache)) {
 				recursion_monitor.erase(cellport.first);
-				return false;
+				goto ret_false;
 			}
 
 		recursion_monitor.erase(cellport.first);
 		muxtree_cells.insert(cellport.first);
 	}
 
-	return true;
+	goto ret_true;
 }
 
 static bool check_state_users(RTLIL::SigSpec sig)
@@ -143,11 +150,12 @@ static void detect_fsm(RTLIL::Wire *wire)
 		pool<Cell*> recursion_monitor;
 		RTLIL::SigSpec sig_q = assign_map(cellport.first->getPort("\\Q"));
 		RTLIL::SigSpec sig_d = assign_map(cellport.first->getPort("\\D"));
+		dict<RTLIL::SigSpec, bool> mux_tree_cache;
 
 		if (sig_q != assign_map(wire))
 			continue;
 
-		looks_like_state_reg = check_state_mux_tree(sig_q, sig_d, recursion_monitor);
+		looks_like_state_reg = check_state_mux_tree(sig_q, sig_d, recursion_monitor, mux_tree_cache);
 		looks_like_good_state_reg = check_state_users(sig_q);
 
 		if (!looks_like_state_reg)
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/opt/Makefile.inc b/passes/opt/Makefile.inc
index 002c1a6a1..3133927bb 100644
--- a/passes/opt/Makefile.inc
+++ b/passes/opt/Makefile.inc
@@ -15,6 +15,7 @@ OBJS += passes/opt/wreduce.o
 OBJS += passes/opt/opt_demorgan.o
 OBJS += passes/opt/rmports.o
 OBJS += passes/opt/opt_lut.o
+OBJS += passes/opt/opt_lut_ins.o
 OBJS += passes/opt/pmux2shiftx.o
 OBJS += passes/opt/muxpack.o
 endif
diff --git a/passes/opt/opt_lut_ins.cc b/passes/opt/opt_lut_ins.cc
new file mode 100644
index 000000000..cf5248ced
--- /dev/null
+++ b/passes/opt/opt_lut_ins.cc
@@ -0,0 +1,278 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/yosys.h"
+#include "kernel/sigtools.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct OptLutInsPass : public Pass {
+	OptLutInsPass() : Pass("opt_lut_ins", "discard unused LUT inputs") { }
+	void help() YS_OVERRIDE
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    opt_lut_ins [options] [selection]\n");
+		log("\n");
+		log("This pass removes unused inputs from LUT cells (that is, inputs that can not\n");
+		log("influence the output signal given this LUT's value).  While such LUTs cannot\n");
+		log("be directly emitted by ABC, they can be a result of various post-ABC\n");
+		log("transformations, such as mapping wide LUTs (not all sub-LUTs will use the\n");
+		log("full set of inputs) or optimizations such as xilinx_dffopt.\n");
+		log("\n");
+		log("    -tech <technology>\n");
+		log("        Instead of generic $lut cells, operate on LUT cells specific\n");
+		log("        to the given technology.  Valid values are: xilinx, ecp5, gowin.\n");
+		log("\n");
+	}
+	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+	{
+		log_header(design, "Executing OPT_LUT_INS pass (discard unused LUT inputs).\n");
+		string techname;
+
+		size_t argidx;
+		for (argidx = 1; argidx < args.size(); argidx++)
+		{
+			if (args[argidx] == "-tech" && argidx+1 < args.size()) {
+				techname = args[++argidx];
+				continue;
+			}
+			break;
+		}
+		extra_args(args, argidx, design);
+
+		if (techname != "" && techname != "xilinx" && techname != "ecp5" && techname != "gowin")
+			log_cmd_error("Unsupported technology: '%s'\n", techname.c_str());
+
+		for (auto module : design->selected_modules())
+		{
+			log("Optimizing LUTs in %s.\n", log_id(module));
+
+			std::vector<Cell *> remove_cells;
+			// Gather LUTs.
+			for (auto cell : module->selected_cells())
+			{
+				if (cell->get_bool_attribute(ID::keep))
+					continue;
+				Const lut;
+				std::vector<SigBit> inputs;
+				std::vector<SigBit> output;
+				bool ignore_const = false;
+				if (techname == "") {
+					if (cell->type != ID($lut))
+						continue;
+					inputs = cell->getPort(ID::A).bits();
+					output = cell->getPort(ID::Y);
+					lut = cell->getParam(ID(LUT));
+				} else if (techname == "xilinx" || techname == "gowin") {
+					if (cell->type == ID(LUT1)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+						};
+					} else if (cell->type == ID(LUT2)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+							cell->getPort(ID(I1)),
+						};
+					} else if (cell->type == ID(LUT3)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+							cell->getPort(ID(I1)),
+							cell->getPort(ID(I2)),
+						};
+					} else if (cell->type == ID(LUT4)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+							cell->getPort(ID(I1)),
+							cell->getPort(ID(I2)),
+							cell->getPort(ID(I3)),
+						};
+					} else if (cell->type == ID(LUT5)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+							cell->getPort(ID(I1)),
+							cell->getPort(ID(I2)),
+							cell->getPort(ID(I3)),
+							cell->getPort(ID(I4)),
+						};
+					} else if (cell->type == ID(LUT6)) {
+						inputs = {
+							cell->getPort(ID(I0)),
+							cell->getPort(ID(I1)),
+							cell->getPort(ID(I2)),
+							cell->getPort(ID(I3)),
+							cell->getPort(ID(I4)),
+							cell->getPort(ID(I5)),
+						};
+					} else {
+						// Not a LUT.
+						continue;
+					}
+					lut = cell->getParam(ID(INIT));
+					if (techname == "xilinx")
+						output = cell->getPort(ID(O));
+					else
+						output = cell->getPort(ID(F));
+				} else if (techname == "ecp5") {
+					if (cell->type == ID(LUT4)) {
+						inputs = {
+							cell->getPort(ID::A),
+							cell->getPort(ID::B),
+							cell->getPort(ID(C)),
+							cell->getPort(ID(D)),
+						};
+						lut = cell->getParam(ID(INIT));
+						output = cell->getPort(ID(Z));
+						ignore_const = true;
+					} else {
+						// Not a LUT.
+						continue;
+					}
+				}
+				std::vector<int> swizzle;
+				std::vector<SigBit> new_inputs;
+				bool doit = false;
+				for (int i = 0; i < GetSize(inputs); i++) {
+					SigBit input = inputs[i];
+					if (!input.wire) {
+						if (input.data == State::S1)
+							swizzle.push_back(-2);
+						else
+							swizzle.push_back(-1);
+						// For ECP5, smaller LUTs are
+						// implemented as LUT4s with
+						// extra const inputs.  Do not
+						// consider that to be a reason
+						// to redo a LUT.
+						if (!ignore_const)
+							doit = true;
+					} else {
+						bool redundant = true;
+						for (int j = 0; j < GetSize(lut); j++) {
+							if (lut[j] != lut[j ^ 1 << i])
+								redundant = false;
+						}
+						if (redundant) {
+							swizzle.push_back(-1);
+							doit = true;
+						} else {
+							swizzle.push_back(GetSize(new_inputs));
+							new_inputs.push_back(input);
+						}
+					}
+				}
+				if (!doit)
+					continue;
+				log("  Optimizing lut %s (%d -> %d)\n", log_id(cell), GetSize(inputs), GetSize(new_inputs));
+				if (techname == "ecp5") {
+					// Pad the LUT to 4 inputs, adding consts from the front.
+					int extra = 4 - GetSize(new_inputs);
+					log_assert(extra >= 0);
+					if (extra) {
+						for (int i = 0; i < extra; i++)
+							new_inputs.insert(new_inputs.begin(), State::S0);
+						for (auto &swz : swizzle)
+							if (swz >= 0)
+								swz += extra;
+					}
+				}
+				Const new_lut(0, 1 << GetSize(new_inputs));
+				for (int i = 0; i < GetSize(new_lut); i++) {
+					int lidx = 0;
+					for (int j = 0; j < GetSize(inputs); j++) {
+						int val;
+						if (swizzle[j] == -2) {
+							val = 1;
+						} else if (swizzle[j] == -1) {
+							val = 0;
+						} else {
+							val = (i >> swizzle[j]) & 1;
+						}
+						lidx |= val << j;
+					}
+					new_lut[i] = lut[lidx];
+				}
+				// For ecp5, do not replace with a const driver — the nextpnr
+				// packer requires a complete set of LUTs for wide LUT muxes.
+				if (new_inputs.empty() && techname != "ecp5") {
+					// const driver.
+					remove_cells.push_back(cell);
+					module->connect(output, new_lut[0]);
+				} else {
+					if (techname == "") {
+						cell->setParam(ID(LUT), new_lut);
+						cell->setParam(ID(WIDTH), GetSize(new_inputs));
+						cell->setPort(ID::A, new_inputs);
+					} else if (techname == "ecp5") {
+						log_assert(GetSize(new_inputs) == 4);
+						cell->setParam(ID(INIT), new_lut);
+						cell->setPort(ID::A, new_inputs[0]);
+						cell->setPort(ID::B, new_inputs[1]);
+						cell->setPort(ID(C), new_inputs[2]);
+						cell->setPort(ID(D), new_inputs[3]);
+					} else {
+						// xilinx, gowin
+						cell->setParam(ID(INIT), new_lut);
+						if (techname == "xilinx")
+							log_assert(GetSize(new_inputs) <= 6);
+						else
+							log_assert(GetSize(new_inputs) <= 4);
+						if (GetSize(new_inputs) == 1)
+							cell->type = ID(LUT1);
+						else if (GetSize(new_inputs) == 2)
+							cell->type = ID(LUT2);
+						else if (GetSize(new_inputs) == 3)
+							cell->type = ID(LUT3);
+						else if (GetSize(new_inputs) == 4)
+							cell->type = ID(LUT4);
+						else if (GetSize(new_inputs) == 5)
+							cell->type = ID(LUT5);
+						else if (GetSize(new_inputs) == 6)
+							cell->type = ID(LUT6);
+						else
+							log_assert(0);
+						cell->unsetPort(ID(I0));
+						cell->unsetPort(ID(I1));
+						cell->unsetPort(ID(I2));
+						cell->unsetPort(ID(I3));
+						cell->unsetPort(ID(I4));
+						cell->unsetPort(ID(I5));
+						cell->setPort(ID(I0), new_inputs[0]);
+						if (GetSize(new_inputs) >= 2)
+							cell->setPort(ID(I1), new_inputs[1]);
+						if (GetSize(new_inputs) >= 3)
+							cell->setPort(ID(I2), new_inputs[2]);
+						if (GetSize(new_inputs) >= 4)
+							cell->setPort(ID(I3), new_inputs[3]);
+						if (GetSize(new_inputs) >= 5)
+							cell->setPort(ID(I4), new_inputs[4]);
+						if (GetSize(new_inputs) >= 6)
+							cell->setPort(ID(I5), new_inputs[5]);
+					}
+				}
+			}
+			for (auto cell : remove_cells)
+				module->remove(cell);
+		}
+	}
+} XilinxDffOptPass;
+
+PRIVATE_NAMESPACE_END
+
diff --git a/passes/opt/opt_reduce.cc b/passes/opt/opt_reduce.cc
index 6a8d8cabd..f74655d1c 100644
--- a/passes/opt/opt_reduce.cc
+++ b/passes/opt/opt_reduce.cc
@@ -44,9 +44,10 @@ struct OptReduceWorker
 		cells.erase(cell);
 
 		RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A));
+		sig_a.sort_and_unify();
 		pool<RTLIL::SigBit> new_sig_a_bits;
 
-		for (auto &bit : sig_a.to_sigbit_set())
+		for (auto &bit : sig_a)
 		{
 			if (bit == RTLIL::State::S0) {
 				if (cell->type == ID($reduce_and)) {
@@ -86,6 +87,7 @@ struct OptReduceWorker
 		}
 
 		RTLIL::SigSpec new_sig_a(new_sig_a_bits);
+		new_sig_a.sort_and_unify();
 
 		if (new_sig_a != sig_a || sig_a.size() != cell->getPort(ID::A).size()) {
 			log("    New input vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_a));
@@ -235,7 +237,6 @@ struct OptReduceWorker
 			log("      New connections: %s = %s\n", log_signal(old_sig_conn.first), log_signal(old_sig_conn.second));
 
 			module->connect(old_sig_conn);
-			module->check();
 
 			did_something = true;
 			total_count++;
@@ -324,6 +325,8 @@ struct OptReduceWorker
 				opt_mux(cell);
 			}
 		}
+
+		module->check();
 	}
 };
 
diff --git a/passes/pmgen/ice40_wrapcarry.cc b/passes/pmgen/ice40_wrapcarry.cc
index 6e154147f..0053c8872 100644
--- a/passes/pmgen/ice40_wrapcarry.cc
+++ b/passes/pmgen/ice40_wrapcarry.cc
@@ -42,11 +42,19 @@ void create_ice40_wrapcarry(ice40_wrapcarry_pm &pm)
 
 	cell->setPort("\\A", st.carry->getPort("\\I0"));
 	cell->setPort("\\B", st.carry->getPort("\\I1"));
-	cell->setPort("\\CI", st.carry->getPort("\\CI"));
+	auto CI = st.carry->getPort("\\CI");
+	cell->setPort("\\CI", CI);
 	cell->setPort("\\CO", st.carry->getPort("\\CO"));
 
 	cell->setPort("\\I0", st.lut->getPort("\\I0"));
-	cell->setPort("\\I3", st.lut->getPort("\\I3"));
+	auto I3 = st.lut->getPort("\\I3");
+	if (pm.sigmap(CI) == pm.sigmap(I3)) {
+		cell->setParam("\\I3_IS_CI", State::S1);
+		I3 = State::Sx;
+	}
+	else
+		cell->setParam("\\I3_IS_CI", State::S0);
+	cell->setPort("\\I3", I3);
 	cell->setPort("\\O", st.lut->getPort("\\O"));
 	cell->setParam("\\LUT", st.lut->getParam("\\LUT_INIT"));
 
@@ -118,7 +126,8 @@ struct Ice40WrapCarryPass : public Pass {
 					auto lut = module->addCell(lut_name, ID($lut));
 					lut->setParam(ID(WIDTH), 4);
 					lut->setParam(ID(LUT), cell->getParam(ID(LUT)));
-					lut->setPort(ID(A), {cell->getPort(ID(I0)), cell->getPort(ID(A)), cell->getPort(ID(B)), cell->getPort(ID(I3)) });
+					auto I3 = cell->getPort(cell->getParam(ID(I3_IS_CI)).as_bool() ? ID(CI) : ID(I3));
+					lut->setPort(ID(A), { I3, cell->getPort(ID(B)), cell->getPort(ID(A)), cell->getPort(ID(I0)) });
 					lut->setPort(ID(Y), cell->getPort(ID(O)));
 
 					Const src;
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/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc
index 4bb4aa047..f9e7783a9 100644
--- a/passes/sat/clk2fflogic.cc
+++ b/passes/sat/clk2fflogic.cc
@@ -214,14 +214,38 @@ struct Clk2fflogicPass : public Pass {
 					continue;
 				}
 
-				if (cell->type.in("$dff", "$adff", "$dffsr"))
+				bool word_dff = cell->type.in("$dff", "$adff", "$dffsr");
+				if (word_dff || cell->type.in(ID($_DFF_N_), ID($_DFF_P_),
+						ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
+						ID($_DFF_PP0_), ID($_DFF_PP1_), ID($_DFF_PN0_), ID($_DFF_PN1_),
+						ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_),
+						ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_)))
 				{
-					bool clkpol = cell->parameters["\\CLK_POLARITY"].as_bool();
+					bool clkpol;
+					SigSpec clk;
+					if (word_dff) {
+						clkpol = cell->parameters["\\CLK_POLARITY"].as_bool();
+						clk = cell->getPort("\\CLK");
+					}
+					else {
+						if (cell->type.in(ID($_DFF_P_), ID($_DFF_N_),
+									ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
+									ID($_DFF_PP0_), ID($_DFF_PP1_), ID($_DFF_PN0_), ID($_DFF_PN1_)))
+							clkpol = cell->type[6] == 'P';
+						else if (cell->type.in(ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_),
+									ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_)))
+							clkpol = cell->type[8] == 'P';
+						else log_abort();
+						clk = cell->getPort("\\C");
+					}
 
-					SigSpec clk = cell->getPort("\\CLK");
 					Wire *past_clk = module->addWire(NEW_ID);
 					past_clk->attributes["\\init"] = clkpol ? State::S1 : State::S0;
-					module->addFf(NEW_ID, clk, past_clk);
+
+					if (word_dff)
+						module->addFf(NEW_ID, clk, past_clk);
+					else
+						module->addFfGate(NEW_ID, clk, past_clk);
 
 					SigSpec sig_d = cell->getPort("\\D");
 					SigSpec sig_q = cell->getPort("\\Q");
@@ -244,8 +268,14 @@ struct Clk2fflogicPass : public Pass {
 
 					Wire *past_d = module->addWire(NEW_ID, GetSize(sig_d));
 					Wire *past_q = module->addWire(NEW_ID, GetSize(sig_q));
-					module->addFf(NEW_ID, sig_d, past_d);
-					module->addFf(NEW_ID, sig_q, past_q);
+					if (word_dff) {
+						module->addFf(NEW_ID, sig_d, past_d);
+						module->addFf(NEW_ID, sig_q, past_q);
+					}
+					else {
+						module->addFfGate(NEW_ID, sig_d, past_d);
+						module->addFfGate(NEW_ID, sig_q, past_q);
+					}
 
 					if (cell->type == "$adff")
 					{
@@ -266,6 +296,26 @@ struct Clk2fflogicPass : public Pass {
 							module->addMux(NEW_ID, rstval, qval, arst, sig_q);
 					}
 					else
+					if (cell->type.in(ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
+						ID($_DFF_PP0_), ID($_DFF_PP1_), ID($_DFF_PN0_), ID($_DFF_PN1_)))
+					{
+						SigSpec arst = cell->getPort("\\R");
+						SigSpec qval = module->MuxGate(NEW_ID, past_q, past_d, clock_edge);
+						SigBit rstval = (cell->type[8] == '1');
+
+						Wire *past_arst = module->addWire(NEW_ID);
+						module->addFfGate(NEW_ID, arst, past_arst);
+						if (cell->type[7] == 'P')
+							arst = module->OrGate(NEW_ID, arst, past_arst);
+						else
+							arst = module->AndGate(NEW_ID, arst, past_arst);
+
+						if (cell->type[7] == 'P')
+							module->addMuxGate(NEW_ID, qval, rstval, arst, sig_q);
+						else
+							module->addMuxGate(NEW_ID, rstval, qval, arst, sig_q);
+					}
+					else
 					if (cell->type == "$dffsr")
 					{
 						SigSpec qval = module->Mux(NEW_ID, past_q, past_d, clock_edge);
@@ -282,9 +332,30 @@ struct Clk2fflogicPass : public Pass {
 						module->addAnd(NEW_ID, qval, clrval, sig_q);
 					}
 					else
+					if (cell->type.in(ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_),
+						ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_)))
+					{
+						SigSpec qval = module->MuxGate(NEW_ID, past_q, past_d, clock_edge);
+						SigSpec setval = cell->getPort("\\S");
+						SigSpec clrval = cell->getPort("\\R");
+
+						if (cell->type[9] != 'P')
+							setval = module->Not(NEW_ID, setval);
+
+						if (cell->type[10] == 'P')
+							clrval = module->Not(NEW_ID, clrval);
+
+						qval = module->OrGate(NEW_ID, qval, setval);
+						module->addAndGate(NEW_ID, qval, clrval, sig_q);
+					}
+					else if (cell->type == "$dff")
 					{
 						module->addMux(NEW_ID, past_q, past_d, clock_edge, sig_q);
 					}
+					else
+					{
+						module->addMuxGate(NEW_ID, past_q, past_d, clock_edge, sig_q);
+					}
 
 					Const initval;
 					bool assign_initval = false;
diff --git a/passes/sat/sat.cc b/passes/sat/sat.cc
index 430bba1e8..436ac1b01 100644
--- a/passes/sat/sat.cc
+++ b/passes/sat/sat.cc
@@ -269,7 +269,8 @@ struct SatHelper
 				for (int i = 0; i < lhs.size(); i++) {
 					RTLIL::SigSpec bit = lhs.extract(i, 1);
 					if (rhs[i] == State::Sx || !satgen.initial_state.check_all(bit)) {
-						removed_bits.append(bit);
+						if (rhs[i] != State::Sx)
+							removed_bits.append(bit);
 						lhs.remove(i, 1);
 						rhs.remove(i, 1);
 						i--;
diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc
index cd357d72a..c16db0d57 100644
--- a/passes/techmap/Makefile.inc
+++ b/passes/techmap/Makefile.inc
@@ -8,9 +8,12 @@ OBJS += passes/techmap/libparse.o
 ifeq ($(ENABLE_ABC),1)
 OBJS += passes/techmap/abc.o
 OBJS += passes/techmap/abc9.o
+OBJS += passes/techmap/abc9_exe.o
+OBJS += passes/techmap/abc9_ops.o
 ifneq ($(ABCEXTERNAL),)
 passes/techmap/abc.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"'
 passes/techmap/abc9.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"'
+passes/techmap/abc9_exe.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"'
 endif
 endif
 
diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc
index 1f6cdaa22..5ae2fb22a 100644
--- a/passes/techmap/abc9.cc
+++ b/passes/techmap/abc9.cc
@@ -2,7 +2,7 @@
  *  yosys -- Yosys Open SYnthesis Suite
  *
  *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
- *                2019  Eddie Hung <eddie@fpgeh.com>
+ *            (C) 2019  Eddie Hung    <eddie@fpgeh.com>
  *
  *  Permission to use, copy, modify, and/or distribute this software for any
  *  purpose with or without fee is hereby granted, provided that the above
@@ -23,714 +23,19 @@
 // http://www.eecs.berkeley.edu/~alanmi/abc/
 
 #include "kernel/register.h"
-#include "kernel/sigtools.h"
 #include "kernel/celltypes.h"
-#include "kernel/cost.h"
+#include "kernel/rtlil.h"
 #include "kernel/log.h"
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <cerrno>
-#include <sstream>
-#include <climits>
 
-#ifndef _WIN32
-#  include <unistd.h>
-#  include <dirent.h>
-#endif
-
-#include "frontends/aiger/aigerparse.h"
-#include "kernel/utils.h"
-
-#ifdef YOSYS_LINK_ABC
-extern "C" int Abc_RealMain(int argc, char *argv[]);
-#endif
+// abc9_exe.cc
+std::string fold_abc9_cmd(std::string str);
 
 USING_YOSYS_NAMESPACE
 PRIVATE_NAMESPACE_BEGIN
 
-int map_autoidx;
-
-inline std::string remap_name(RTLIL::IdString abc9_name)
+struct Abc9Pass : public ScriptPass
 {
-	return stringf("$abc$%d$%s", map_autoidx, abc9_name.c_str()+1);
-}
-
-void handle_loops(RTLIL::Design *design, RTLIL::Module *module)
-{
-	Pass::call(design, "scc -set_attr abc9_scc_id {} % w:*");
-
-	// For every unique SCC found, (arbitrarily) find the first
-	// cell in the component, and select (and mark) all its output
-	// wires
-	pool<RTLIL::Const> ids_seen;
-	for (auto cell : module->cells()) {
-		auto it = cell->attributes.find(ID(abc9_scc_id));
-		if (it != cell->attributes.end()) {
-			auto r = ids_seen.insert(it->second);
-			if (r.second) {
-				for (auto &c : cell->connections_) {
-					if (c.second.is_fully_const()) continue;
-					if (cell->output(c.first)) {
-						SigBit b = c.second.as_bit();
-						Wire *w = b.wire;
-						if (w->port_input) {
-							// In this case, hopefully the loop break has been already created
-							// Get the non-prefixed wire
-							Wire *wo = module->wire(stringf("%s.abco", b.wire->name.c_str()));
-							log_assert(wo != nullptr);
-							log_assert(wo->port_output);
-							log_assert(b.offset < GetSize(wo));
-							c.second = RTLIL::SigBit(wo, b.offset);
-						}
-						else {
-							// Create a new output/input loop break
-							w->port_input = true;
-							w = module->wire(stringf("%s.abco", w->name.c_str()));
-							if (!w) {
-								w = module->addWire(stringf("%s.abco", b.wire->name.c_str()), GetSize(b.wire));
-								w->port_output = true;
-							}
-							else {
-								log_assert(w->port_input);
-								log_assert(b.offset < GetSize(w));
-							}
-							w->set_bool_attribute(ID(abc9_scc_break));
-							c.second = RTLIL::SigBit(w, b.offset);
-						}
-					}
-				}
-			}
-			cell->attributes.erase(it);
-		}
-	}
-
-	module->fixup_ports();
-}
-
-std::string add_echos_to_abc9_cmd(std::string str)
-{
-	std::string new_str, token;
-	for (size_t i = 0; i < str.size(); i++) {
-		token += str[i];
-		if (str[i] == ';') {
-			while (i+1 < str.size() && str[i+1] == ' ')
-				i++;
-			new_str += "echo + " + token + " " + token + " ";
-			token.clear();
-		}
-	}
-
-	if (!token.empty()) {
-		if (!new_str.empty())
-			new_str += "echo + " + token + "; ";
-		new_str += token;
-	}
-
-	return new_str;
-}
-
-std::string fold_abc9_cmd(std::string str)
-{
-	std::string token, new_str = "          ";
-	int char_counter = 10;
-
-	for (size_t i = 0; i <= str.size(); i++) {
-		if (i < str.size())
-			token += str[i];
-		if (i == str.size() || str[i] == ';') {
-			if (char_counter + token.size() > 75)
-				new_str += "\n              ", char_counter = 14;
-			new_str += token, char_counter += token.size();
-			token.clear();
-		}
-	}
-
-	return new_str;
-}
-
-std::string replace_tempdir(std::string text, std::string tempdir_name, bool show_tempdir)
-{
-	if (show_tempdir)
-		return text;
-
-	while (1) {
-		size_t pos = text.find(tempdir_name);
-		if (pos == std::string::npos)
-			break;
-		text = text.substr(0, pos) + "<abc-temp-dir>" + text.substr(pos + GetSize(tempdir_name));
-	}
-
-	std::string  selfdir_name = proc_self_dirname();
-	if (selfdir_name != "/") {
-		while (1) {
-			size_t pos = text.find(selfdir_name);
-			if (pos == std::string::npos)
-				break;
-			text = text.substr(0, pos) + "<yosys-exe-dir>/" + text.substr(pos + GetSize(selfdir_name));
-		}
-	}
-
-	return text;
-}
-
-struct abc9_output_filter
-{
-	bool got_cr;
-	int escape_seq_state;
-	std::string linebuf;
-	std::string tempdir_name;
-	bool show_tempdir;
-
-	abc9_output_filter(std::string tempdir_name, bool show_tempdir) : tempdir_name(tempdir_name), show_tempdir(show_tempdir)
-	{
-		got_cr = false;
-		escape_seq_state = 0;
-	}
-
-	void next_char(char ch)
-	{
-		if (escape_seq_state == 0 && ch == '\033') {
-			escape_seq_state = 1;
-			return;
-		}
-		if (escape_seq_state == 1) {
-			escape_seq_state = ch == '[' ? 2 : 0;
-			return;
-		}
-		if (escape_seq_state == 2) {
-			if ((ch < '0' || '9' < ch) && ch != ';')
-				escape_seq_state = 0;
-			return;
-		}
-		escape_seq_state = 0;
-		if (ch == '\r') {
-			got_cr = true;
-			return;
-		}
-		if (ch == '\n') {
-			log("ABC: %s\n", replace_tempdir(linebuf, tempdir_name, show_tempdir).c_str());
-			got_cr = false, linebuf.clear();
-			return;
-		}
-		if (got_cr)
-			got_cr = false, linebuf.clear();
-		linebuf += ch;
-	}
-
-	void next_line(const std::string &line)
-	{
-		//int pi, po;
-		//if (sscanf(line.c_str(), "Start-point = pi%d.  End-point = po%d.", &pi, &po) == 2) {
-		//	log("ABC: Start-point = pi%d (%s).  End-point = po%d (%s).\n",
-		//			pi, pi_map.count(pi) ? pi_map.at(pi).c_str() : "???",
-		//			po, po_map.count(po) ? po_map.at(po).c_str() : "???");
-		//	return;
-		//}
-
-		for (char ch : line)
-			next_char(ch);
-	}
-};
-
-void abc9_module(RTLIL::Design *design, RTLIL::Module *module, std::string script_file, std::string exe_file,
-		bool cleanup, vector<int> lut_costs, bool dff_mode, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode,
-		bool show_tempdir, std::string box_file, std::string lut_file,
-		std::string wire_delay
-)
-{
-	map_autoidx = autoidx++;
-
-	std::string tempdir_name = "/tmp/yosys-abc-XXXXXX";
-	if (!cleanup)
-		tempdir_name[0] = tempdir_name[4] = '_';
-	tempdir_name = make_temp_dir(tempdir_name);
-	log_header(design, "Extracting gate netlist of module `%s' to `%s/input.xaig'..\n",
-			module->name.c_str(), replace_tempdir(tempdir_name, tempdir_name, show_tempdir).c_str());
-
-	std::string abc9_script;
-
-	if (!lut_costs.empty()) {
-		abc9_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str());
-		if (!box_file.empty())
-			abc9_script += stringf("read_box %s; ", box_file.c_str());
-	}
-	else
-	if (!lut_file.empty()) {
-		abc9_script += stringf("read_lut %s; ", lut_file.c_str());
-		if (!box_file.empty())
-			abc9_script += stringf("read_box %s; ", box_file.c_str());
-	}
-	else
-		log_abort();
-
-	abc9_script += stringf("&read %s/input.xaig; &ps; ", tempdir_name.c_str());
-
-	if (!script_file.empty()) {
-		if (script_file[0] == '+') {
-			for (size_t i = 1; i < script_file.size(); i++)
-				if (script_file[i] == '\'')
-					abc9_script += "'\\''";
-				else if (script_file[i] == ',')
-					abc9_script += " ";
-				else
-					abc9_script += script_file[i];
-		} else
-			abc9_script += stringf("source %s", script_file.c_str());
-	} else if (!lut_costs.empty() || !lut_file.empty()) {
-		abc9_script += fast_mode ? RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos)
-			: RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos);
-	} else
-		log_abort();
-
-	for (size_t pos = abc9_script.find("{D}"); pos != std::string::npos; pos = abc9_script.find("{D}", pos))
-		abc9_script = abc9_script.substr(0, pos) + delay_target + abc9_script.substr(pos+3);
-
-	//for (size_t pos = abc9_script.find("{S}"); pos != std::string::npos; pos = abc9_script.find("{S}", pos))
-	//	abc9_script = abc9_script.substr(0, pos) + lutin_shared + abc9_script.substr(pos+3);
-
-	for (size_t pos = abc9_script.find("{W}"); pos != std::string::npos; pos = abc9_script.find("{W}", pos))
-		abc9_script = abc9_script.substr(0, pos) + wire_delay + abc9_script.substr(pos+3);
-
-	std::string C;
-	if (design->scratchpad.count("abc9.if.C"))
-		C = "-C " + design->scratchpad_get_string("abc9.if.C");
-	for (size_t pos = abc9_script.find("{C}"); pos != std::string::npos; pos = abc9_script.find("{C}", pos))
-		abc9_script = abc9_script.substr(0, pos) + C + abc9_script.substr(pos+3);
-
-	std::string R;
-	if (design->scratchpad.count("abc9.if.R"))
-		R = "-R " + design->scratchpad_get_string("abc9.if.R");
-	for (size_t pos = abc9_script.find("{R}"); pos != std::string::npos; pos = abc9_script.find("{R}", pos))
-		abc9_script = abc9_script.substr(0, pos) + R + abc9_script.substr(pos+3);
-
-	abc9_script += stringf("; &ps -l; &write -n %s/output.aig;", tempdir_name.c_str());
-	if (design->scratchpad_get_bool("abc9.verify")) {
-		if (dff_mode)
-			abc9_script += "verify -s;";
-		else
-			abc9_script += "verify;";
-	}
-	abc9_script += "time";
-	abc9_script = add_echos_to_abc9_cmd(abc9_script);
-
-	for (size_t i = 0; i+1 < abc9_script.size(); i++)
-		if (abc9_script[i] == ';' && abc9_script[i+1] == ' ')
-			abc9_script[i+1] = '\n';
-
-	FILE *f = fopen(stringf("%s/abc.script", tempdir_name.c_str()).c_str(), "wt");
-	fprintf(f, "%s\n", abc9_script.c_str());
-	fclose(f);
-
-	log_push();
-
-	handle_loops(design, module);
-
-	Pass::call(design, "aigmap -select");
-
-	Pass::call(design, stringf("write_xaiger -map %s/input.sym %s/input.xaig", tempdir_name.c_str(), tempdir_name.c_str()));
-
-	int count_outputs = design->scratchpad_get_int("write_xaiger.num_outputs");
-	log("Extracted %d AND gates and %d wires to a netlist network with %d inputs and %d outputs.\n",
-			design->scratchpad_get_int("write_xaiger.num_ands"),
-			design->scratchpad_get_int("write_xaiger.num_wires"),
-			design->scratchpad_get_int("write_xaiger.num_inputs"),
-			count_outputs);
-
-	if (count_outputs > 0) {
-		std::string buffer;
-		std::ifstream ifs;
-#if 0
-		buffer = stringf("%s/%s", tempdir_name.c_str(), "input.xaig");
-		ifs.open(buffer);
-		if (ifs.fail())
-			log_error("Can't open ABC output file `%s'.\n", buffer.c_str());
-		buffer = stringf("%s/%s", tempdir_name.c_str(), "input.sym");
-		log_assert(!design->module(ID($__abc9__)));
-		{
-			AigerReader reader(design, ifs, ID($__abc9__), "" /* clk_name */, buffer.c_str() /* map_filename */, true /* wideports */);
-			reader.parse_xaiger();
-		}
-		ifs.close();
-		Pass::call_on_module(design, design->module(ID($__abc9__)), stringf("write_verilog -noexpr -norename -selected"));
-		design->remove(design->module(ID($__abc9__)));
-#endif
-
-		log_header(design, "Executing ABC9.\n");
-
-		if (!lut_costs.empty()) {
-			buffer = stringf("%s/lutdefs.txt", tempdir_name.c_str());
-			f = fopen(buffer.c_str(), "wt");
-			if (f == NULL)
-				log_error("Opening %s for writing failed: %s\n", buffer.c_str(), strerror(errno));
-			for (int i = 0; i < GetSize(lut_costs); i++)
-				fprintf(f, "%d %d.00 1.00\n", i+1, lut_costs.at(i));
-			fclose(f);
-		}
-
-		buffer = stringf("%s -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str());
-		log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, show_tempdir).c_str());
-
-#ifndef YOSYS_LINK_ABC
-		abc9_output_filter filt(tempdir_name, show_tempdir);
-		int ret = run_command(buffer, std::bind(&abc9_output_filter::next_line, filt, std::placeholders::_1));
-#else
-		// These needs to be mutable, supposedly due to getopt
-		char *abc9_argv[5];
-		string tmp_script_name = stringf("%s/abc.script", tempdir_name.c_str());
-		abc9_argv[0] = strdup(exe_file.c_str());
-		abc9_argv[1] = strdup("-s");
-		abc9_argv[2] = strdup("-f");
-		abc9_argv[3] = strdup(tmp_script_name.c_str());
-		abc9_argv[4] = 0;
-		int ret = Abc_RealMain(4, abc9_argv);
-		free(abc9_argv[0]);
-		free(abc9_argv[1]);
-		free(abc9_argv[2]);
-		free(abc9_argv[3]);
-#endif
-		if (ret != 0)
-			log_error("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret);
-
-		buffer = stringf("%s/%s", tempdir_name.c_str(), "output.aig");
-		ifs.open(buffer, std::ifstream::binary);
-		if (ifs.fail())
-			log_error("Can't open ABC output file `%s'.\n", buffer.c_str());
-
-		buffer = stringf("%s/%s", tempdir_name.c_str(), "input.sym");
-		log_assert(!design->module(ID($__abc9__)));
-
-		AigerReader reader(design, ifs, ID($__abc9__), "" /* clk_name */, buffer.c_str() /* map_filename */, true /* wideports */);
-		reader.parse_xaiger();
-		ifs.close();
-
-#if 0
-		Pass::call_on_module(design, design->module(ID($__abc9__)), stringf("write_verilog -noexpr -norename -selected"));
-#endif
-
-		log_header(design, "Re-integrating ABC9 results.\n");
-		RTLIL::Module *mapped_mod = design->module(ID($__abc9__));
-		if (mapped_mod == NULL)
-			log_error("ABC output file does not contain a module `$__abc9__'.\n");
-
-		for (auto w : mapped_mod->wires())
-			module->addWire(remap_name(w->name), GetSize(w));
-
-		dict<IdString, bool> abc9_box;
-		vector<RTLIL::Cell*> boxes;
-		for (auto cell : module->cells().to_vector()) {
-			if (cell->type.in(ID($_AND_), ID($_NOT_), ID($__ABC9_FF_))) {
-				module->remove(cell);
-				continue;
-			}
-			RTLIL::Module* box_module = design->module(cell->type);
-			auto jt = abc9_box.find(cell->type);
-			if (jt == abc9_box.end())
-				jt = abc9_box.insert(std::make_pair(cell->type, box_module && box_module->attributes.count(ID(abc9_box_id)))).first;
-			if (jt->second) {
-				if (box_module->get_bool_attribute("\\abc9_flop")) {
-					if (dff_mode)
-						boxes.emplace_back(cell);
-					else
-						box_module->set_bool_attribute("\\abc9_keep", false);
-				}
-				else
-					boxes.emplace_back(cell);
-			}
-		}
-
-		dict<SigBit, pool<IdString>> bit_drivers, bit_users;
-		TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
-		dict<RTLIL::Cell*,RTLIL::Cell*> not2drivers;
-		dict<SigBit, std::vector<RTLIL::Cell*>> bit2sinks;
-
-		std::map<IdString, int> cell_stats;
-		for (auto mapped_cell : mapped_mod->cells())
-		{
-			toposort.node(mapped_cell->name);
-
-			RTLIL::Cell *cell = nullptr;
-			if (mapped_cell->type == ID($_NOT_)) {
-				RTLIL::SigBit a_bit = mapped_cell->getPort(ID::A);
-				RTLIL::SigBit y_bit = mapped_cell->getPort(ID::Y);
-				bit_users[a_bit].insert(mapped_cell->name);
-				bit_drivers[y_bit].insert(mapped_cell->name);
-
-				if (!a_bit.wire) {
-					mapped_cell->setPort(ID::Y, module->addWire(NEW_ID));
-					RTLIL::Wire *wire = module->wire(remap_name(y_bit.wire->name));
-					log_assert(wire);
-					module->connect(RTLIL::SigBit(wire, y_bit.offset), State::S1);
-				}
-				else if (!lut_costs.empty() || !lut_file.empty()) {
-					RTLIL::Cell* driver_lut = nullptr;
-					// ABC can return NOT gates that drive POs
-					if (!a_bit.wire->port_input) {
-						// If it's not a NOT gate that that comes from a PI directly,
-						// find the driver LUT and clone that to guarantee that we won't
-						// increase the max logic depth
-						// (TODO: Optimise by not cloning unless will increase depth)
-						RTLIL::IdString driver_name;
-						if (GetSize(a_bit.wire) == 1)
-							driver_name = stringf("$lut%s", a_bit.wire->name.c_str());
-						else
-							driver_name = stringf("$lut%s[%d]", a_bit.wire->name.c_str(), a_bit.offset);
-						driver_lut = mapped_mod->cell(driver_name);
-					}
-
-					if (!driver_lut) {
-						// If a driver couldn't be found (could be from PI or box CI)
-						// then implement using a LUT
-						cell = module->addLut(remap_name(stringf("$lut%s", mapped_cell->name.c_str())),
-								RTLIL::SigBit(module->wires_.at(remap_name(a_bit.wire->name)), a_bit.offset),
-								RTLIL::SigBit(module->wires_.at(remap_name(y_bit.wire->name)), y_bit.offset),
-								RTLIL::Const::from_string("01"));
-						bit2sinks[cell->getPort(ID::A)].push_back(cell);
-						cell_stats[ID($lut)]++;
-					}
-					else
-						not2drivers[mapped_cell] = driver_lut;
-					continue;
-				}
-				else
-					log_abort();
-				continue;
-			}
-			cell_stats[mapped_cell->type]++;
-
-			RTLIL::Cell *existing_cell = nullptr;
-			if (mapped_cell->type.in(ID($lut), ID($__ABC9_FF_))) {
-				if (mapped_cell->type == ID($lut) &&
-						GetSize(mapped_cell->getPort(ID::A)) == 1 &&
-						mapped_cell->getParam(ID(LUT)) == RTLIL::Const::from_string("01")) {
-					SigSpec my_a = module->wires_.at(remap_name(mapped_cell->getPort(ID::A).as_wire()->name));
-					SigSpec my_y = module->wires_.at(remap_name(mapped_cell->getPort(ID::Y).as_wire()->name));
-					module->connect(my_y, my_a);
-					log_abort();
-					continue;
-				}
-				cell = module->addCell(remap_name(mapped_cell->name), mapped_cell->type);
-			}
-			else {
-				existing_cell = module->cell(mapped_cell->name);
-				log_assert(existing_cell);
-				cell = module->addCell(remap_name(mapped_cell->name), mapped_cell->type);
-			}
-
-			if (existing_cell) {
-				cell->parameters = existing_cell->parameters;
-				cell->attributes = existing_cell->attributes;
-			}
-			else {
-				cell->parameters = mapped_cell->parameters;
-				cell->attributes = mapped_cell->attributes;
-			}
-
-			RTLIL::Module* box_module = design->module(mapped_cell->type);
-			auto abc9_flop = box_module && box_module->get_bool_attribute("\\abc9_flop");
-			for (auto &conn : mapped_cell->connections()) {
-				RTLIL::SigSpec newsig;
-				for (auto c : conn.second.chunks()) {
-					if (c.width == 0)
-						continue;
-					//log_assert(c.width == 1);
-					if (c.wire)
-						c.wire = module->wires_.at(remap_name(c.wire->name));
-					newsig.append(c);
-				}
-				cell->setPort(conn.first, newsig);
-
-				if (!abc9_flop) {
-					if (cell->input(conn.first)) {
-						for (auto i : newsig)
-							bit2sinks[i].push_back(cell);
-						for (auto i : conn.second)
-							bit_users[i].insert(mapped_cell->name);
-					}
-					if (cell->output(conn.first))
-						for (auto i : conn.second)
-							bit_drivers[i].insert(mapped_cell->name);
-				}
-			}
-		}
-
-		for (auto existing_cell : boxes) {
-			Cell *cell = module->cell(remap_name(existing_cell->name));
-			if (cell) {
-				for (auto &conn : existing_cell->connections()) {
-					if (!conn.second.is_wire())
-						continue;
-					Wire *wire = conn.second.as_wire();
-					if (!wire->get_bool_attribute(ID(abc9_padding)))
-						continue;
-					cell->unsetPort(conn.first);
-					log_debug("Dropping padded port connection for %s (%s) .%s (%s )\n", log_id(cell), cell->type.c_str(), log_id(conn.first), log_signal(conn.second));
-				}
-				module->swap_names(cell, existing_cell);
-			}
-			module->remove(existing_cell);
-		}
-
-		// Copy connections (and rename) from mapped_mod to module
-		for (auto conn : mapped_mod->connections()) {
-			if (!conn.first.is_fully_const()) {
-				auto chunks = conn.first.chunks();
-				for (auto &c : chunks)
-					c.wire = module->wires_.at(remap_name(c.wire->name));
-				conn.first = std::move(chunks);
-			}
-			if (!conn.second.is_fully_const()) {
-				auto chunks = conn.second.chunks();
-				for (auto &c : chunks)
-					if (c.wire)
-						c.wire = module->wires_.at(remap_name(c.wire->name));
-				conn.second = std::move(chunks);
-			}
-			module->connect(conn);
-		}
-
-		for (auto &it : cell_stats)
-			log("ABC RESULTS:   %15s cells: %8d\n", it.first.c_str(), it.second);
-		int in_wires = 0, out_wires = 0;
-
-		// Stitch in mapped_mod's inputs/outputs into module
-		for (auto port : mapped_mod->ports) {
-			RTLIL::Wire *w = mapped_mod->wire(port);
-			RTLIL::Wire *wire = module->wire(port);
-			log_assert(wire);
-			RTLIL::Wire *remap_wire = module->wire(remap_name(port));
-			RTLIL::SigSpec signal = RTLIL::SigSpec(wire, 0, GetSize(remap_wire));
-			log_assert(GetSize(signal) >= GetSize(remap_wire));
-
-			RTLIL::SigSig conn;
-			if (w->port_output) {
-				conn.first = signal;
-				conn.second = remap_wire;
-				out_wires++;
-				module->connect(conn);
-			}
-			else if (w->port_input) {
-				conn.first = remap_wire;
-				conn.second = signal;
-				in_wires++;
-				module->connect(conn);
-			}
-		}
-
-		for (auto &it : bit_users)
-			if (bit_drivers.count(it.first))
-				for (auto driver_cell : bit_drivers.at(it.first))
-				for (auto user_cell : it.second)
-					toposort.edge(driver_cell, user_cell);
-		bool no_loops YS_ATTRIBUTE(unused) = toposort.sort();
-		log_assert(no_loops);
-
-		for (auto ii = toposort.sorted.rbegin(); ii != toposort.sorted.rend(); ii++) {
-			RTLIL::Cell *not_cell = mapped_mod->cell(*ii);
-			log_assert(not_cell);
-			if (not_cell->type != ID($_NOT_))
-				continue;
-			auto it = not2drivers.find(not_cell);
-			if (it == not2drivers.end())
-				continue;
-			RTLIL::Cell *driver_lut = it->second;
-			RTLIL::SigBit a_bit = not_cell->getPort(ID::A);
-			RTLIL::SigBit y_bit = not_cell->getPort(ID::Y);
-			RTLIL::Const driver_mask;
-
-			a_bit.wire = module->wires_.at(remap_name(a_bit.wire->name));
-			y_bit.wire = module->wires_.at(remap_name(y_bit.wire->name));
-
-			auto jt = bit2sinks.find(a_bit);
-			if (jt == bit2sinks.end())
-				goto clone_lut;
-
-			for (auto sink_cell : jt->second)
-				if (sink_cell->type != ID($lut))
-					goto clone_lut;
-
-			// Push downstream LUTs past inverter
-			for (auto sink_cell : jt->second) {
-				SigSpec A = sink_cell->getPort(ID::A);
-				RTLIL::Const mask = sink_cell->getParam(ID(LUT));
-				int index = 0;
-				for (; index < GetSize(A); index++)
-					if (A[index] == a_bit)
-						break;
-				log_assert(index < GetSize(A));
-				int i = 0;
-				while (i < GetSize(mask)) {
-					for (int j = 0; j < (1 << index); j++)
-						std::swap(mask[i+j], mask[i+j+(1 << index)]);
-					i += 1 << (index+1);
-				}
-				A[index] = y_bit;
-				sink_cell->setPort(ID::A, A);
-				sink_cell->setParam(ID(LUT), mask);
-			}
-
-			// Since we have rewritten all sinks (which we know
-			// to be only LUTs) to be after the inverter, we can
-			// go ahead and clone the LUT with the expectation
-			// that the original driving LUT will become dangling
-			// and get cleaned away
-clone_lut:
-			driver_mask = driver_lut->getParam(ID(LUT));
-			for (auto &b : driver_mask.bits) {
-				if (b == RTLIL::State::S0) b = RTLIL::State::S1;
-				else if (b == RTLIL::State::S1) b = RTLIL::State::S0;
-			}
-			auto cell = module->addLut(NEW_ID,
-					driver_lut->getPort(ID::A),
-					y_bit,
-					driver_mask);
-			for (auto &bit : cell->connections_.at(ID::A)) {
-				bit.wire = module->wires_.at(remap_name(bit.wire->name));
-				bit2sinks[bit].push_back(cell);
-			}
-		}
-
-		// Now 'unexpose' those wires by undoing
-		// the expose operation -- remove them from PO/PI
-		// and re-connecting them back together
-		for (auto wire : module->wires()) {
-			auto it = wire->attributes.find(ID(abc9_scc_break));
-			if (it != wire->attributes.end()) {
-				wire->attributes.erase(it);
-				log_assert(wire->port_output);
-				wire->port_output = false;
-				std::string name = wire->name.str();
-				RTLIL::Wire *i_wire = module->wire(name.substr(0, GetSize(name) - 5));
-				log_assert(i_wire);
-				log_assert(i_wire->port_input);
-				i_wire->port_input = false;
-				module->connect(i_wire, wire);
-			}
-		}
-		module->fixup_ports();
-
-		//log("ABC RESULTS:        internal signals: %8d\n", int(signal_list.size()) - in_wires - out_wires);
-		log("ABC RESULTS:           input signals: %8d\n", in_wires);
-		log("ABC RESULTS:          output signals: %8d\n", out_wires);
-
-		design->remove(mapped_mod);
-	}
-	else
-	{
-		log("Don't call ABC as there is nothing to map.\n");
-	}
-
-	if (cleanup)
-	{
-		log("Removing temp directory.\n");
-		remove_directory(tempdir_name);
-	}
-
-	log_pop();
-}
-
-struct Abc9Pass : public Pass {
-	Abc9Pass() : Pass("abc9", "use ABC9 for technology mapping") { }
+	Abc9Pass() : ScriptPass("abc9", "use ABC9 for technology mapping") { }
 	void on_register() YS_OVERRIDE
 	{
 		RTLIL::constpad["abc9.script.default"] = "+&scorr; &sweep; &dc2; &dch -f; &ps; &if {C} {W} {D} {R} -v; &mfs";
@@ -782,8 +87,14 @@ struct Abc9Pass : public Pass {
 		log("\n");
 		log("    abc9 [options] [selection]\n");
 		log("\n");
-		log("This pass uses the ABC tool [1] for technology mapping of yosys's internal gate\n");
-		log("library to a target architecture. Only fully-selected modules are supported.\n");
+		log("This script pass performs a sequence of commands to facilitate the use of the ABC\n");
+		log("tool [1] for technology mapping of the current design to a target FPGA\n");
+		log("architecture. Only fully-selected modules are supported.\n");
+		log("\n");
+		log("    -run <from_label>:<to_label>\n");
+		log("        only run the commands between the labels (see below). an empty\n");
+		log("        from label is synonymous to 'begin', and empty to label is\n");
+		log("        synonymous to the end of the command list.\n");
 		log("\n");
 		log("    -exe <command>\n");
 #ifdef ABCEXTERNAL
@@ -802,15 +113,11 @@ struct Abc9Pass : public Pass {
 		log("        replaced with blanks before the string is passed to ABC.\n");
 		log("\n");
 		log("        if no -script parameter is given, the following scripts are used:\n");
-		log("\n");
-		log("        for -lut/-luts:\n");
 		log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos)).c_str());
 		log("\n");
 		log("    -fast\n");
 		log("        use different default scripts that are slightly faster (at the cost\n");
 		log("        of output quality):\n");
-		log("\n");
-		log("        for -lut/-luts:\n");
 		log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos)).c_str());
 		log("\n");
 		log("    -D <picoseconds>\n");
@@ -851,7 +158,7 @@ struct Abc9Pass : public Pass {
 		log("        command output is identical across runs.\n");
 		log("\n");
 		log("    -box <file>\n");
-		log("        pass this file with box library to ABC. Use with -lut.\n");
+		log("        pass this file with box library to ABC.\n");
 		log("\n");
 		log("Note that this is a logic optimization pass within Yosys that is calling ABC\n");
 		log("internally. This is not going to \"run ABC on your design\". It will instead run\n");
@@ -862,233 +169,176 @@ struct Abc9Pass : public Pass {
 		log("\n");
 		log("[1] http://www.eecs.berkeley.edu/~alanmi/abc/\n");
 		log("\n");
+		help_script();
+		log("\n");
 	}
+
+	std::stringstream exe_cmd;
+	bool dff_mode, cleanup;
+	std::string box_file;
+
+	void clear_flags() YS_OVERRIDE
+	{
+		exe_cmd.str("");
+		exe_cmd << "abc9_exe";
+		dff_mode = false;
+		cleanup = true;
+		box_file.clear();
+	}
+
 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
 	{
-		log_header(design, "Executing ABC9 pass (technology mapping using ABC9).\n");
-		log_push();
-
-#ifdef ABCEXTERNAL
-		std::string exe_file = ABCEXTERNAL;
-#else
-		std::string exe_file = proc_self_dirname() + "yosys-abc";
-#endif
-		std::string script_file, clk_str, box_file, lut_file;
-		std::string delay_target, lutin_shared = "-S 1", wire_delay;
-		bool fast_mode = false, dff_mode = false, cleanup = true;
-		bool show_tempdir = false;
-		vector<int> lut_costs;
-
-#if 0
-		cleanup = false;
-		show_tempdir = true;
-#endif
-
-#ifdef _WIN32
-#ifndef ABCEXTERNAL
-		if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\yosys-abc.exe"))
-			exe_file = proc_self_dirname() + "..\\yosys-abc";
-#endif
-#endif
+		std::string run_from, run_to;
+		clear_flags();
 
 		// get arguments from scratchpad first, then override by command arguments
-		std::string lut_arg, luts_arg;
-		exe_file = design->scratchpad_get_string("abc9.exe", exe_file /* inherit default value if not set */);
-		script_file = design->scratchpad_get_string("abc9.script", script_file);
-		if (design->scratchpad.count("abc9.D")) {
-			delay_target = "-D " + design->scratchpad_get_string("abc9.D");
-		}
-		lut_arg = design->scratchpad_get_string("abc9.lut", lut_arg);
-		luts_arg = design->scratchpad_get_string("abc9.luts", luts_arg);
-		fast_mode = design->scratchpad_get_bool("abc9.fast", fast_mode);
 		dff_mode = design->scratchpad_get_bool("abc9.dff", dff_mode);
 		cleanup = !design->scratchpad_get_bool("abc9.nocleanup", !cleanup);
-		show_tempdir = design->scratchpad_get_bool("abc9.showtmp", show_tempdir);
-		box_file = design->scratchpad_get_string("abc9.box", box_file);
-		if (design->scratchpad.count("abc9.W")) {
-			wire_delay = "-W " + design->scratchpad_get_string("abc9.W");
-		}
 
 		if (design->scratchpad_get_bool("abc9.debug")) {
 			cleanup = false;
-			show_tempdir = true;
+			exe_cmd << " -showtmp";
 		}
 
 		size_t argidx;
-		char pwd [PATH_MAX];
-		if (!getcwd(pwd, sizeof(pwd))) {
-			log_cmd_error("getcwd failed: %s\n", strerror(errno));
-			log_abort();
-		}
 		for (argidx = 1; argidx < args.size(); argidx++) {
 			std::string arg = args[argidx];
-			if (arg == "-exe" && argidx+1 < args.size()) {
-				exe_file = args[++argidx];
+			if ((arg == "-exe" || arg == "-script" || arg == "-D" ||
+						/* arg == "-S" || */ arg == "-lut" || arg == "-luts" ||
+						/*arg == "-box" ||*/ arg == "-W") &&
+					argidx+1 < args.size()) {
+				exe_cmd << " " << arg << " " << args[++argidx];
 				continue;
 			}
-			if (arg == "-script" && argidx+1 < args.size()) {
-				script_file = args[++argidx];
-				continue;
-			}
-			if (arg == "-D" && argidx+1 < args.size()) {
-				delay_target = "-D " + args[++argidx];
-				continue;
-			}
-			//if (arg == "-S" && argidx+1 < args.size()) {
-			//	lutin_shared = "-S " + args[++argidx];
-			//	continue;
-			//}
-			if (arg == "-lut" && argidx+1 < args.size()) {
-				lut_arg = args[++argidx];
-				continue;
-			}
-			if (arg == "-luts" && argidx+1 < args.size()) {
-				luts_arg = args[++argidx];
-				continue;
-			}
-			if (arg == "-fast") {
-				fast_mode = true;
+			if (arg == "-fast" || /* arg == "-dff" || */
+					/* arg == "-nocleanup" || */ arg == "-showtmp") {
+				exe_cmd << " " << arg;
 				continue;
 			}
 			if (arg == "-dff") {
 				dff_mode = true;
+				exe_cmd << " " << arg;
 				continue;
 			}
 			if (arg == "-nocleanup") {
 				cleanup = false;
 				continue;
 			}
-			if (arg == "-showtmp") {
-				show_tempdir = true;
-				continue;
-			}
 			if (arg == "-box" && argidx+1 < args.size()) {
 				box_file = args[++argidx];
 				continue;
 			}
-			if (arg == "-W" && argidx+1 < args.size()) {
-				wire_delay = "-W " + args[++argidx];
+			if (arg == "-run" && argidx+1 < args.size()) {
+				size_t pos = args[argidx+1].find(':');
+				if (pos == std::string::npos)
+					break;
+				run_from = args[++argidx].substr(0, pos);
+				run_to = args[argidx].substr(pos+1);
 				continue;
 			}
 			break;
 		}
 		extra_args(args, argidx, design);
 
-		rewrite_filename(script_file);
-		if (!script_file.empty() && !is_absolute_path(script_file) && script_file[0] != '+')
-			script_file = std::string(pwd) + "/" + script_file;
-
-		// handle -lut / -luts args
-		if (!lut_arg.empty()) {
-			string arg = lut_arg;
-			if (arg.find_first_not_of("0123456789:") == std::string::npos) {
-				size_t pos = arg.find_first_of(':');
-				int lut_mode = 0, lut_mode2 = 0;
-				if (pos != string::npos) {
-					lut_mode = atoi(arg.substr(0, pos).c_str());
-					lut_mode2 = atoi(arg.substr(pos+1).c_str());
-				} else {
-					lut_mode = atoi(arg.c_str());
-					lut_mode2 = lut_mode;
-				}
-				lut_costs.clear();
-				for (int i = 0; i < lut_mode; i++)
-					lut_costs.push_back(1);
-				for (int i = lut_mode; i < lut_mode2; i++)
-					lut_costs.push_back(2 << (i - lut_mode));
-			}
-			else {
-				lut_file = arg;
-				rewrite_filename(lut_file);
-				if (!lut_file.empty() && !is_absolute_path(lut_file) && lut_file[0] != '+')
-					lut_file = std::string(pwd) + "/" + lut_file;
-			}
-		}
-		if (!luts_arg.empty()) {
-			lut_costs.clear();
-			for (auto &tok : split_tokens(luts_arg, ",")) {
-				auto parts = split_tokens(tok, ":");
-				if (GetSize(parts) == 0 && !lut_costs.empty())
-					lut_costs.push_back(lut_costs.back());
-				else if (GetSize(parts) == 1)
-					lut_costs.push_back(atoi(parts.at(0).c_str()));
-				else if (GetSize(parts) == 2)
-					while (GetSize(lut_costs) < atoi(parts.at(0).c_str()))
-						lut_costs.push_back(atoi(parts.at(1).c_str()));
-				else
-					log_cmd_error("Invalid -luts syntax.\n");
-			}
+		log_assert(design);
+		if (design->selected_modules().empty()) {
+			log_warning("No modules selected for ABC9 techmapping.\n");
+			return;
 		}
 
-		// ABC expects a box file for XAIG
-		if (box_file.empty())
-		    box_file = "+/dummy.box";
+		log_header(design, "Executing ABC9 pass.\n");
+		log_push();
 
-		rewrite_filename(box_file);
-		if (!box_file.empty() && !is_absolute_path(box_file) && box_file[0] != '+')
-		    box_file = std::string(pwd) + "/" + box_file;
-
-		SigMap assign_map;
-		CellTypes ct(design);
-		for (auto module : design->selected_modules())
-		{
-			if (module->processes.size() > 0) {
-				log("Skipping module %s as it contains processes.\n", log_id(module));
-				continue;
-			}
-			log_assert(!module->attributes.count(ID(abc9_box_id)));
-
-			if (!design->selected_whole_module(module))
-				log_error("Can't handle partially selected module %s!\n", log_id(module));
-
-			assign_map.set(module);
-
-			typedef SigSpec clkdomain_t;
-			dict<clkdomain_t, int> clk_to_mergeability;
-
-			if (dff_mode)
-				for (auto cell : module->cells()) {
-					if (cell->type != "$__ABC9_FF_")
-						continue;
-
-					Wire *abc9_clock_wire = module->wire(stringf("%s.clock", cell->name.c_str()));
-					if (abc9_clock_wire == NULL)
-						log_error("'%s.clock' is not a wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
-					SigSpec abc9_clock = assign_map(abc9_clock_wire);
-
-					clkdomain_t key(abc9_clock);
-
-					auto r = clk_to_mergeability.insert(std::make_pair(abc9_clock, clk_to_mergeability.size() + 1));
-					auto r2 YS_ATTRIBUTE(unused) = cell->attributes.insert(std::make_pair(ID(abc9_mergeability), r.first->second));
-					log_assert(r2.second);
-
-					Wire *abc9_init_wire = module->wire(stringf("%s.init", cell->name.c_str()));
-					if (abc9_init_wire == NULL)
-						log_error("'%s.init' is not a wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
-					log_assert(GetSize(abc9_init_wire) == 1);
-					SigSpec abc9_init = assign_map(abc9_init_wire);
-					if (!abc9_init.is_fully_const())
-						log_error("'%s.init' is not a constant wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
-					r2 = cell->attributes.insert(std::make_pair(ID(abc9_init), abc9_init.as_const()));
-					log_assert(r2.second);
-				}
-			else
-				for (auto cell : module->cells()) {
-					auto inst_module = design->module(cell->type);
-					if (!inst_module || !inst_module->get_bool_attribute("\\abc9_flop"))
-						continue;
-					cell->set_bool_attribute("\\abc9_keep");
-				}
-
-			design->selected_active_module = module->name.str();
-			abc9_module(design, module, script_file, exe_file, cleanup, lut_costs, dff_mode,
-					delay_target, lutin_shared, fast_mode, show_tempdir,
-					box_file, lut_file, wire_delay);
-			design->selected_active_module.clear();
-		}
+		run_script(design, run_from, run_to);
 
 		log_pop();
 	}
+
+	void script() YS_OVERRIDE
+	{
+		if (check_label("pre")) {
+			run("abc9_ops -check");
+			run("scc -set_attr abc9_scc_id {}");
+			if (help_mode)
+				run("abc9_ops -mark_scc -prep_delays -prep_xaiger [-dff]", "(option for -dff)");
+			else
+				run("abc9_ops -mark_scc -prep_delays -prep_xaiger" + std::string(dff_mode ? " -dff" : ""), "(option for -dff)");
+			run("select -set abc9_holes A:abc9_holes");
+			run("flatten -wb @abc9_holes");
+			run("techmap @abc9_holes");
+			if (dff_mode || help_mode)
+				run("abc9_ops -prep_dff", "(only if -dff)");
+			run("opt -purge @abc9_holes");
+			run("aigmap");
+			run("wbflip @abc9_holes");
+		}
+
+		if (check_label("map")) {
+			if (help_mode) {
+				run("foreach module in selection");
+				run("    abc9_ops -write_box [<value from -box>|(null)] <abc-temp-dir>/input.box");
+				run("    write_xaiger -map <abc-temp-dir>/input.sym <abc-temp-dir>/input.xaig");
+				run("    abc9_exe [options] -cwd <abc-temp-dir> -box <abc-temp-dir>/input.box");
+				run("    read_aiger -xaiger -wideports -module_name <module-name>$abc9 -map <abc-temp-dir>/input.sym <abc-temp-dir>/output.aig");
+				run("    abc9_ops -reintegrate");
+			}
+			else {
+				auto selected_modules = active_design->selected_modules();
+				active_design->selection_stack.emplace_back(false);
+
+				for (auto mod : selected_modules) {
+					if (mod->processes.size() > 0) {
+						log("Skipping module %s as it contains processes.\n", log_id(mod));
+						continue;
+					}
+					log_assert(!mod->attributes.count(ID(abc9_box_id)));
+
+					log_push();
+					active_design->selection().select(mod);
+
+					if (!active_design->selected_whole_module(mod))
+						log_error("Can't handle partially selected module %s!\n", log_id(mod));
+
+					std::string tempdir_name = "/tmp/yosys-abc-XXXXXX";
+					if (!cleanup)
+						tempdir_name[0] = tempdir_name[4] = '_';
+					tempdir_name = make_temp_dir(tempdir_name);
+
+					if (box_file.empty())
+						run(stringf("abc9_ops -write_box (null) %s/input.box", tempdir_name.c_str()));
+					else
+						run(stringf("abc9_ops -write_box %s %s/input.box", box_file.c_str(), tempdir_name.c_str()));
+					run(stringf("write_xaiger -map %s/input.sym %s/input.xaig", tempdir_name.c_str(), tempdir_name.c_str()));
+
+					int num_outputs = active_design->scratchpad_get_int("write_xaiger.num_outputs");
+
+					log("Extracted %d AND gates and %d wires from module `%s' to a netlist network with %d inputs and %d outputs.\n",
+							active_design->scratchpad_get_int("write_xaiger.num_ands"),
+							active_design->scratchpad_get_int("write_xaiger.num_wires"),
+							log_id(mod),
+							active_design->scratchpad_get_int("write_xaiger.num_inputs"),
+							num_outputs);
+					if (num_outputs) {
+						run(stringf("%s -cwd %s -box %s/input.box", exe_cmd.str().c_str(), tempdir_name.c_str(), tempdir_name.c_str()));
+						run(stringf("read_aiger -xaiger -wideports -module_name %s$abc9 -map %s/input.sym %s/output.aig", log_id(mod), tempdir_name.c_str(), tempdir_name.c_str()));
+						run("abc9_ops -reintegrate");
+					}
+					else
+						log("Don't call ABC as there is nothing to map.\n");
+
+					if (cleanup) {
+						log("Removing temp directory.\n");
+						remove_directory(tempdir_name);
+					}
+
+					active_design->selection().selected_modules.clear();
+					log_pop();
+				}
+
+				active_design->selection_stack.pop_back();
+			}
+		}
+	}
 } Abc9Pass;
 
 PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc
new file mode 100644
index 000000000..71221951c
--- /dev/null
+++ b/passes/techmap/abc9_exe.cc
@@ -0,0 +1,530 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *                2019  Eddie Hung <eddie@fpgeh.com>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+// [[CITE]] ABC
+// Berkeley Logic Synthesis and Verification Group, ABC: A System for Sequential Synthesis and Verification
+// http://www.eecs.berkeley.edu/~alanmi/abc/
+
+#include "kernel/register.h"
+#include "kernel/log.h"
+
+#ifndef _WIN32
+#  include <unistd.h>
+#  include <dirent.h>
+#endif
+
+#ifdef YOSYS_LINK_ABC
+extern "C" int Abc_RealMain(int argc, char *argv[]);
+#endif
+
+std::string fold_abc9_cmd(std::string str)
+{
+	std::string token, new_str = "          ";
+	int char_counter = 10;
+
+	for (size_t i = 0; i <= str.size(); i++) {
+		if (i < str.size())
+			token += str[i];
+		if (i == str.size() || str[i] == ';') {
+			if (char_counter + token.size() > 75)
+				new_str += "\n              ", char_counter = 14;
+			new_str += token, char_counter += token.size();
+			token.clear();
+		}
+	}
+
+	return new_str;
+}
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+std::string add_echos_to_abc9_cmd(std::string str)
+{
+	std::string new_str, token;
+	for (size_t i = 0; i < str.size(); i++) {
+		token += str[i];
+		if (str[i] == ';') {
+			while (i+1 < str.size() && str[i+1] == ' ')
+				i++;
+			new_str += "echo + " + token + " " + token + " ";
+			token.clear();
+		}
+	}
+
+	if (!token.empty()) {
+		if (!new_str.empty())
+			new_str += "echo + " + token + "; ";
+		new_str += token;
+	}
+
+	return new_str;
+}
+
+std::string replace_tempdir(std::string text, std::string tempdir_name, bool show_tempdir)
+{
+	if (show_tempdir)
+		return text;
+
+	while (1) {
+		size_t pos = text.find(tempdir_name);
+		if (pos == std::string::npos)
+			break;
+		text = text.substr(0, pos) + "<abc-temp-dir>" + text.substr(pos + GetSize(tempdir_name));
+	}
+
+	std::string  selfdir_name = proc_self_dirname();
+	if (selfdir_name != "/") {
+		while (1) {
+			size_t pos = text.find(selfdir_name);
+			if (pos == std::string::npos)
+				break;
+			text = text.substr(0, pos) + "<yosys-exe-dir>/" + text.substr(pos + GetSize(selfdir_name));
+		}
+	}
+
+	return text;
+}
+
+struct abc9_output_filter
+{
+	bool got_cr;
+	int escape_seq_state;
+	std::string linebuf;
+	std::string tempdir_name;
+	bool show_tempdir;
+
+	abc9_output_filter(std::string tempdir_name, bool show_tempdir) : tempdir_name(tempdir_name), show_tempdir(show_tempdir)
+	{
+		got_cr = false;
+		escape_seq_state = 0;
+	}
+
+	void next_char(char ch)
+	{
+		if (escape_seq_state == 0 && ch == '\033') {
+			escape_seq_state = 1;
+			return;
+		}
+		if (escape_seq_state == 1) {
+			escape_seq_state = ch == '[' ? 2 : 0;
+			return;
+		}
+		if (escape_seq_state == 2) {
+			if ((ch < '0' || '9' < ch) && ch != ';')
+				escape_seq_state = 0;
+			return;
+		}
+		escape_seq_state = 0;
+		if (ch == '\r') {
+			got_cr = true;
+			return;
+		}
+		if (ch == '\n') {
+			log("ABC: %s\n", replace_tempdir(linebuf, tempdir_name, show_tempdir).c_str());
+			got_cr = false, linebuf.clear();
+			return;
+		}
+		if (got_cr)
+			got_cr = false, linebuf.clear();
+		linebuf += ch;
+	}
+
+	void next_line(const std::string &line)
+	{
+		//int pi, po;
+		//if (sscanf(line.c_str(), "Start-point = pi%d.  End-point = po%d.", &pi, &po) == 2) {
+		//	log("ABC: Start-point = pi%d (%s).  End-point = po%d (%s).\n",
+		//			pi, pi_map.count(pi) ? pi_map.at(pi).c_str() : "???",
+		//			po, po_map.count(po) ? po_map.at(po).c_str() : "???");
+		//	return;
+		//}
+
+		for (char ch : line)
+			next_char(ch);
+	}
+};
+
+void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe_file,
+		vector<int> lut_costs, bool dff_mode, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode,
+		bool show_tempdir, std::string box_file, std::string lut_file,
+		std::string wire_delay, std::string tempdir_name
+)
+{
+	std::string abc9_script;
+
+	if (!lut_costs.empty())
+		abc9_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str());
+	else if (!lut_file.empty())
+		abc9_script += stringf("read_lut %s; ", lut_file.c_str());
+	else
+		log_abort();
+
+	log_assert(!box_file.empty());
+	abc9_script += stringf("read_box %s; ", box_file.c_str());
+	abc9_script += stringf("&read %s/input.xaig; &ps; ", tempdir_name.c_str());
+
+	if (!script_file.empty()) {
+		if (script_file[0] == '+') {
+			for (size_t i = 1; i < script_file.size(); i++)
+				if (script_file[i] == '\'')
+					abc9_script += "'\\''";
+				else if (script_file[i] == ',')
+					abc9_script += " ";
+				else
+					abc9_script += script_file[i];
+		} else
+			abc9_script += stringf("source %s", script_file.c_str());
+	} else if (!lut_costs.empty() || !lut_file.empty()) {
+		abc9_script += fast_mode ? RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos)
+			: RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos);
+	} else
+		log_abort();
+
+	for (size_t pos = abc9_script.find("{D}"); pos != std::string::npos; pos = abc9_script.find("{D}", pos))
+		abc9_script = abc9_script.substr(0, pos) + delay_target + abc9_script.substr(pos+3);
+
+	//for (size_t pos = abc9_script.find("{S}"); pos != std::string::npos; pos = abc9_script.find("{S}", pos))
+	//	abc9_script = abc9_script.substr(0, pos) + lutin_shared + abc9_script.substr(pos+3);
+
+	for (size_t pos = abc9_script.find("{W}"); pos != std::string::npos; pos = abc9_script.find("{W}", pos))
+		abc9_script = abc9_script.substr(0, pos) + wire_delay + abc9_script.substr(pos+3);
+
+	std::string C;
+	if (design->scratchpad.count("abc9.if.C"))
+		C = "-C " + design->scratchpad_get_string("abc9.if.C");
+	for (size_t pos = abc9_script.find("{C}"); pos != std::string::npos; pos = abc9_script.find("{C}", pos))
+		abc9_script = abc9_script.substr(0, pos) + C + abc9_script.substr(pos+3);
+
+	std::string R;
+	if (design->scratchpad.count("abc9.if.R"))
+		R = "-R " + design->scratchpad_get_string("abc9.if.R");
+	for (size_t pos = abc9_script.find("{R}"); pos != std::string::npos; pos = abc9_script.find("{R}", pos))
+		abc9_script = abc9_script.substr(0, pos) + R + abc9_script.substr(pos+3);
+
+	abc9_script += stringf("; &ps -l; &write -n %s/output.aig", tempdir_name.c_str());
+	if (design->scratchpad_get_bool("abc9.verify")) {
+		if (dff_mode)
+			abc9_script += "; verify -s";
+		else
+			abc9_script += "; verify";
+	}
+	abc9_script += "; time";
+	abc9_script = add_echos_to_abc9_cmd(abc9_script);
+
+	for (size_t i = 0; i+1 < abc9_script.size(); i++)
+		if (abc9_script[i] == ';' && abc9_script[i+1] == ' ')
+			abc9_script[i+1] = '\n';
+
+	FILE *f = fopen(stringf("%s/abc.script", tempdir_name.c_str()).c_str(), "wt");
+	fprintf(f, "%s\n", abc9_script.c_str());
+	fclose(f);
+
+	std::string buffer;
+
+	log_header(design, "Executing ABC9.\n");
+
+	if (!lut_costs.empty()) {
+		buffer = stringf("%s/lutdefs.txt", tempdir_name.c_str());
+		f = fopen(buffer.c_str(), "wt");
+		if (f == NULL)
+			log_error("Opening %s for writing failed: %s\n", buffer.c_str(), strerror(errno));
+		for (int i = 0; i < GetSize(lut_costs); i++)
+			fprintf(f, "%d %d.00 1.00\n", i+1, lut_costs.at(i));
+		fclose(f);
+	}
+
+	buffer = stringf("%s -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str());
+	log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, show_tempdir).c_str());
+
+#ifndef YOSYS_LINK_ABC
+	abc9_output_filter filt(tempdir_name, show_tempdir);
+	int ret = run_command(buffer, std::bind(&abc9_output_filter::next_line, filt, std::placeholders::_1));
+#else
+	// These needs to be mutable, supposedly due to getopt
+	char *abc9_argv[5];
+	string tmp_script_name = stringf("%s/abc.script", tempdir_name.c_str());
+	abc9_argv[0] = strdup(exe_file.c_str());
+	abc9_argv[1] = strdup("-s");
+	abc9_argv[2] = strdup("-f");
+	abc9_argv[3] = strdup(tmp_script_name.c_str());
+	abc9_argv[4] = 0;
+	int ret = Abc_RealMain(4, abc9_argv);
+	free(abc9_argv[0]);
+	free(abc9_argv[1]);
+	free(abc9_argv[2]);
+	free(abc9_argv[3]);
+#endif
+	if (ret != 0)
+		log_error("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret);
+}
+
+struct Abc9ExePass : public Pass {
+	Abc9ExePass() : Pass("abc9_exe", "use ABC9 for technology mapping") { }
+	void help() YS_OVERRIDE
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    abc9_exe [options]\n");
+		log("\n");
+		log(" \n");
+		log("This pass uses the ABC tool [1] for technology mapping of the top module\n");
+		log("(according to the (* top *) attribute or if only one module is currently selected)\n");
+		log("to a target FPGA architecture.\n");
+		log("\n");
+		log("    -exe <command>\n");
+#ifdef ABCEXTERNAL
+		log("        use the specified command instead of \"" ABCEXTERNAL "\" to execute ABC.\n");
+#else
+		log("        use the specified command instead of \"<yosys-bindir>/yosys-abc\" to execute ABC.\n");
+#endif
+		log("        This can e.g. be used to call a specific version of ABC or a wrapper.\n");
+		log("\n");
+		log("    -script <file>\n");
+		log("        use the specified ABC script file instead of the default script.\n");
+		log("\n");
+		log("        if <file> starts with a plus sign (+), then the rest of the filename\n");
+		log("        string is interpreted as the command string to be passed to ABC. The\n");
+		log("        leading plus sign is removed and all commas (,) in the string are\n");
+		log("        replaced with blanks before the string is passed to ABC.\n");
+		log("\n");
+		log("        if no -script parameter is given, the following scripts are used:\n");
+		log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos)).c_str());
+		log("\n");
+		log("    -fast\n");
+		log("        use different default scripts that are slightly faster (at the cost\n");
+		log("        of output quality):\n");
+		log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos)).c_str());
+		log("\n");
+		log("    -D <picoseconds>\n");
+		log("        set delay target. the string {D} in the default scripts above is\n");
+		log("        replaced by this option when used, and an empty string otherwise\n");
+		log("        (indicating best possible delay).\n");
+		log("\n");
+//		log("    -S <num>\n");
+//		log("        maximum number of LUT inputs shared.\n");
+//		log("        (replaces {S} in the default scripts above, default: -S 1)\n");
+//		log("\n");
+		log("    -lut <width>\n");
+		log("        generate netlist using luts of (max) the specified width.\n");
+		log("\n");
+		log("    -lut <w1>:<w2>\n");
+		log("        generate netlist using luts of (max) the specified width <w2>. All\n");
+		log("        luts with width <= <w1> have constant cost. for luts larger than <w1>\n");
+		log("        the area cost doubles with each additional input bit. the delay cost\n");
+		log("        is still constant for all lut widths.\n");
+		log("\n");
+		log("    -lut <file>\n");
+		log("        pass this file with lut library to ABC.\n");
+		log("\n");
+		log("    -luts <cost1>,<cost2>,<cost3>,<sizeN>:<cost4-N>,..\n");
+		log("        generate netlist using luts. Use the specified costs for luts with 1,\n");
+		log("        2, 3, .. inputs.\n");
+		log("\n");
+		log("    -showtmp\n");
+		log("        print the temp dir name in log. usually this is suppressed so that the\n");
+		log("        command output is identical across runs.\n");
+		log("\n");
+		log("    -box <file>\n");
+		log("        pass this file with box library to ABC.\n");
+		log("\n");
+		log("    -cwd <dir>\n");
+		log("        use this as the current working directory, inside which the 'input.xaig'\n");
+		log("        file is expected. temporary files will be created in this directory, and\n");
+		log("        the mapped result will be written to 'output.aig'.\n");
+		log("\n");
+		log("Note that this is a logic optimization pass within Yosys that is calling ABC\n");
+		log("internally. This is not going to \"run ABC on your design\". It will instead run\n");
+		log("ABC on logic snippets extracted from your design. You will not get any useful\n");
+		log("output when passing an ABC script that writes a file. Instead write your full\n");
+		log("design as BLIF file with write_blif and then load that into ABC externally if\n");
+		log("you want to use ABC to convert your design into another format.\n");
+		log("\n");
+		log("[1] http://www.eecs.berkeley.edu/~alanmi/abc/\n");
+		log("\n");
+	}
+	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+	{
+		log_header(design, "Executing ABC9_MAP pass (technology mapping using ABC9).\n");
+
+#ifdef ABCEXTERNAL
+		std::string exe_file = ABCEXTERNAL;
+#else
+		std::string exe_file = proc_self_dirname() + "yosys-abc";
+#endif
+		std::string script_file, clk_str, box_file, lut_file;
+		std::string delay_target, lutin_shared = "-S 1", wire_delay;
+		std::string tempdir_name;
+		bool fast_mode = false, dff_mode = false;
+		bool show_tempdir = false;
+		vector<int> lut_costs;
+
+#if 0
+		cleanup = false;
+		show_tempdir = true;
+#endif
+
+#ifdef _WIN32
+#ifndef ABCEXTERNAL
+		if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\yosys-abc.exe"))
+			exe_file = proc_self_dirname() + "..\\yosys-abc";
+#endif
+#endif
+
+		std::string lut_arg, luts_arg;
+		exe_file = design->scratchpad_get_string("abc9.exe", exe_file /* inherit default value if not set */);
+		script_file = design->scratchpad_get_string("abc9.script", script_file);
+		if (design->scratchpad.count("abc9.D")) {
+			delay_target = "-D " + design->scratchpad_get_string("abc9.D");
+		}
+		lut_arg = design->scratchpad_get_string("abc9.lut", lut_arg);
+		luts_arg = design->scratchpad_get_string("abc9.luts", luts_arg);
+		fast_mode = design->scratchpad_get_bool("abc9.fast", fast_mode);
+		dff_mode = design->scratchpad_get_bool("abc9.dff", dff_mode);
+		show_tempdir = design->scratchpad_get_bool("abc9.showtmp", show_tempdir);
+		box_file = design->scratchpad_get_string("abc9.box", box_file);
+		if (design->scratchpad.count("abc9.W")) {
+			wire_delay = "-W " + design->scratchpad_get_string("abc9.W");
+		}
+
+		size_t argidx;
+		char pwd [PATH_MAX];
+		if (!getcwd(pwd, sizeof(pwd))) {
+			log_cmd_error("getcwd failed: %s\n", strerror(errno));
+			log_abort();
+		}
+		for (argidx = 1; argidx < args.size(); argidx++) {
+			std::string arg = args[argidx];
+			if (arg == "-exe" && argidx+1 < args.size()) {
+				exe_file = args[++argidx];
+				continue;
+			}
+			if (arg == "-script" && argidx+1 < args.size()) {
+				script_file = args[++argidx];
+				continue;
+			}
+			if (arg == "-D" && argidx+1 < args.size()) {
+				delay_target = "-D " + args[++argidx];
+				continue;
+			}
+			//if (arg == "-S" && argidx+1 < args.size()) {
+			//	lutin_shared = "-S " + args[++argidx];
+			//	continue;
+			//}
+			if (arg == "-lut" && argidx+1 < args.size()) {
+				lut_arg = args[++argidx];
+				continue;
+			}
+			if (arg == "-luts" && argidx+1 < args.size()) {
+				lut_arg = args[++argidx];
+				continue;
+			}
+			if (arg == "-fast") {
+				fast_mode = true;
+				continue;
+			}
+			if (arg == "-dff") {
+				dff_mode = true;
+				continue;
+			}
+			if (arg == "-showtmp") {
+				show_tempdir = true;
+				continue;
+			}
+			if (arg == "-box" && argidx+1 < args.size()) {
+				box_file = args[++argidx];
+				continue;
+			}
+			if (arg == "-W" && argidx+1 < args.size()) {
+				wire_delay = "-W " + args[++argidx];
+				continue;
+			}
+			if (arg == "-cwd" && argidx+1 < args.size()) {
+				tempdir_name = args[++argidx];
+				continue;
+			}
+			break;
+		}
+		extra_args(args, argidx, design);
+
+		rewrite_filename(script_file);
+		if (!script_file.empty() && !is_absolute_path(script_file) && script_file[0] != '+')
+			script_file = std::string(pwd) + "/" + script_file;
+
+		// handle -lut / -luts args
+		if (!lut_arg.empty()) {
+			string arg = lut_arg;
+			if (arg.find_first_not_of("0123456789:") == std::string::npos) {
+				size_t pos = arg.find_first_of(':');
+				int lut_mode = 0, lut_mode2 = 0;
+				if (pos != string::npos) {
+					lut_mode = atoi(arg.substr(0, pos).c_str());
+					lut_mode2 = atoi(arg.substr(pos+1).c_str());
+				} else {
+					lut_mode = atoi(arg.c_str());
+					lut_mode2 = lut_mode;
+				}
+				lut_costs.clear();
+				for (int i = 0; i < lut_mode; i++)
+					lut_costs.push_back(1);
+				for (int i = lut_mode; i < lut_mode2; i++)
+					lut_costs.push_back(2 << (i - lut_mode));
+			}
+			else {
+				lut_file = arg;
+				rewrite_filename(lut_file);
+				if (!lut_file.empty() && !is_absolute_path(lut_file) && lut_file[0] != '+')
+					lut_file = std::string(pwd) + "/" + lut_file;
+			}
+		}
+		if (!luts_arg.empty()) {
+			lut_costs.clear();
+			for (auto &tok : split_tokens(luts_arg, ",")) {
+				auto parts = split_tokens(tok, ":");
+				if (GetSize(parts) == 0 && !lut_costs.empty())
+					lut_costs.push_back(lut_costs.back());
+				else if (GetSize(parts) == 1)
+					lut_costs.push_back(atoi(parts.at(0).c_str()));
+				else if (GetSize(parts) == 2)
+					while (GetSize(lut_costs) < atoi(parts.at(0).c_str()))
+						lut_costs.push_back(atoi(parts.at(1).c_str()));
+				else
+					log_cmd_error("Invalid -luts syntax.\n");
+			}
+		}
+
+		if (box_file.empty())
+			log_cmd_error("abc9_exe '-box' option is mandatory.\n");
+
+		rewrite_filename(box_file);
+		if (!box_file.empty() && !is_absolute_path(box_file) && box_file[0] != '+')
+			box_file = std::string(pwd) + "/" + box_file;
+
+		if (tempdir_name.empty())
+			log_cmd_error("abc9_exe '-cwd' option is mandatory.\n");
+
+
+		abc9_module(design, script_file, exe_file, lut_costs, dff_mode,
+				delay_target, lutin_shared, fast_mode, show_tempdir,
+				box_file, lut_file, wire_delay, tempdir_name);
+	}
+} Abc9ExePass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc
new file mode 100644
index 000000000..2b4a5c802
--- /dev/null
+++ b/passes/techmap/abc9_ops.cc
@@ -0,0 +1,1120 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *                2019  Eddie Hung <eddie@fpgeh.com>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/register.h"
+#include "kernel/sigtools.h"
+#include "kernel/utils.h"
+#include "kernel/celltypes.h"
+
+#define ABC9_FLOPS_BASE_ID 8000
+#define ABC9_DELAY_BASE_ID 9000
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+int map_autoidx;
+
+inline std::string remap_name(RTLIL::IdString abc9_name)
+{
+	return stringf("$abc$%d$%s", map_autoidx, abc9_name.c_str()+1);
+}
+
+void check(RTLIL::Design *design)
+{
+	dict<IdString,IdString> box_lookup;
+	for (auto m : design->modules()) {
+		if (m->name.begins_with("$paramod"))
+			continue;
+
+		auto flop = m->get_bool_attribute(ID(abc9_flop));
+		auto it = m->attributes.find(ID(abc9_box_id));
+		if (!flop) {
+			if (it == m->attributes.end())
+				continue;
+			auto id = it->second.as_int();
+			auto r = box_lookup.insert(std::make_pair(stringf("$__boxid%d", id), m->name));
+			if (!r.second)
+				log_error("Module '%s' has the same abc9_box_id = %d value as '%s'.\n",
+						log_id(m), id, log_id(r.first->second));
+		}
+
+		// Make carry in the last PI, and carry out the last PO
+		//   since ABC requires it this way
+		IdString carry_in, carry_out;
+		for (const auto &port_name : m->ports) {
+			auto w = m->wire(port_name);
+			log_assert(w);
+			if (w->get_bool_attribute("\\abc9_carry")) {
+				if (w->port_input) {
+					if (carry_in != IdString())
+						log_error("Module '%s' contains more than one (* abc9_carry *) input port.\n", log_id(m));
+					carry_in = port_name;
+				}
+				if (w->port_output) {
+					if (carry_out != IdString())
+						log_error("Module '%s' contains more than one (* abc9_carry *) output port.\n", log_id(m));
+					carry_out = port_name;
+				}
+			}
+
+			auto it = w->attributes.find("\\abc9_arrival");
+			if (it != w->attributes.end()) {
+				int count = 0;
+				if (it->second.flags == 0) {
+					if (it->second.as_int() < 0)
+						log_error("%s.%s has negative arrival value %d!\n", log_id(m), log_id(port_name),
+								it->second.as_int());
+					count++;
+				}
+				else
+					for (const auto &tok : split_tokens(it->second.decode_string())) {
+						if (tok.find_first_not_of("0123456789") != std::string::npos)
+							log_error("%s.%s has non-integer arrival value '%s'!\n", log_id(m), log_id(port_name),
+									tok.c_str());
+						if (atoi(tok.c_str()) < 0)
+							log_error("%s.%s has negative arrival value %s!\n", log_id(m), log_id(port_name),
+									tok.c_str());
+						count++;
+					}
+				if (count > 1 && count != GetSize(w))
+					log_error("%s.%s is %d bits wide but abc9_arrival = %s has %d value(s)!\n", log_id(m), log_id(port_name),
+							GetSize(w), log_signal(it->second), count);
+			}
+
+			it = w->attributes.find("\\abc9_required");
+			if (it != w->attributes.end()) {
+				int count = 0;
+				if (it->second.flags == 0) {
+					if (it->second.as_int() < 0)
+						log_error("%s.%s has negative required value %d!\n", log_id(m), log_id(port_name),
+								it->second.as_int());
+					count++;
+				}
+				else
+					for (const auto &tok : split_tokens(it->second.decode_string())) {
+						if (tok.find_first_not_of("0123456789") != std::string::npos)
+							log_error("%s.%s has non-integer required value '%s'!\n", log_id(m), log_id(port_name),
+									tok.c_str());
+						if (atoi(tok.c_str()) < 0)
+							log_error("%s.%s has negative required value %s!\n", log_id(m), log_id(port_name),
+									tok.c_str());
+						count++;
+					}
+				if (count > 1 && count != GetSize(w))
+					log_error("%s.%s is %d bits wide but abc9_required = %s has %d value(s)!\n", log_id(m), log_id(port_name),
+							GetSize(w), log_signal(it->second), count);
+			}
+		}
+
+		if (carry_in != IdString() && carry_out == IdString())
+			log_error("Module '%s' contains an (* abc9_carry *) input port but no output port.\n", log_id(m));
+		if (carry_in == IdString() && carry_out != IdString())
+			log_error("Module '%s' contains an (* abc9_carry *) output port but no input port.\n", log_id(m));
+
+		if (flop) {
+			int num_outputs = 0;
+			for (auto port_name : m->ports) {
+				auto wire = m->wire(port_name);
+				if (wire->port_output) num_outputs++;
+			}
+			if (num_outputs != 1)
+				log_error("Module '%s' with (* abc9_flop *) has %d outputs (expect 1).\n", log_id(m), num_outputs);
+		}
+	}
+}
+
+void mark_scc(RTLIL::Module *module)
+{
+	// For every unique SCC found, (arbitrarily) find the first
+	//   cell in the component, and convert all wires driven by
+	//   its output ports into a new PO, and drive its previous
+	//   sinks with a new PI
+	pool<RTLIL::Const> ids_seen;
+	for (auto cell : module->cells()) {
+		auto it = cell->attributes.find(ID(abc9_scc_id));
+		if (it == cell->attributes.end())
+			continue;
+		auto id = it->second;
+		auto r = ids_seen.insert(id);
+		cell->attributes.erase(it);
+		if (!r.second)
+			continue;
+		for (auto &c : cell->connections_) {
+			if (c.second.is_fully_const()) continue;
+			if (cell->output(c.first)) {
+				SigBit b = c.second.as_bit();
+				Wire *w = b.wire;
+				w->set_bool_attribute(ID::keep);
+				w->attributes[ID(abc9_scc_id)] = id.as_int();
+			}
+		}
+	}
+
+	module->fixup_ports();
+}
+
+void prep_dff(RTLIL::Module *module)
+{
+	auto design = module->design;
+	log_assert(design);
+
+	SigMap assign_map(module);
+
+	typedef SigSpec clkdomain_t;
+	dict<clkdomain_t, int> clk_to_mergeability;
+
+	for (auto cell : module->cells()) {
+		if (cell->type != "$__ABC9_FF_")
+			continue;
+
+		Wire *abc9_clock_wire = module->wire(stringf("%s.clock", cell->name.c_str()));
+		if (abc9_clock_wire == NULL)
+			log_error("'%s.clock' is not a wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
+		SigSpec abc9_clock = assign_map(abc9_clock_wire);
+
+		clkdomain_t key(abc9_clock);
+
+		auto r = clk_to_mergeability.insert(std::make_pair(abc9_clock, clk_to_mergeability.size() + 1));
+		auto r2 YS_ATTRIBUTE(unused) = cell->attributes.insert(std::make_pair(ID(abc9_mergeability), r.first->second));
+		log_assert(r2.second);
+
+		Wire *abc9_init_wire = module->wire(stringf("%s.init", cell->name.c_str()));
+		if (abc9_init_wire == NULL)
+			log_error("'%s.init' is not a wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
+		log_assert(GetSize(abc9_init_wire) == 1);
+		SigSpec abc9_init = assign_map(abc9_init_wire);
+		if (!abc9_init.is_fully_const())
+			log_error("'%s.init' is not a constant wire present in module '%s'.\n", cell->name.c_str(), log_id(module));
+		if (abc9_init == State::S1)
+			log_error("'%s.init' in module '%s' has value 1'b1 which is not supported by 'abc9 -dff'.\n", cell->name.c_str(), log_id(module));
+		r2 = cell->attributes.insert(std::make_pair(ID(abc9_init), abc9_init.as_const()));
+		log_assert(r2.second);
+	}
+
+	RTLIL::Module *holes_module = design->module(stringf("%s$holes", module->name.c_str()));
+	if (holes_module) {
+		SigMap sigmap(holes_module);
+
+		dict<SigSpec, SigSpec> replace;
+		for (auto cell : holes_module->cells().to_vector()) {
+			if (!cell->type.in("$_DFF_N_", "$_DFF_NN0_", "$_DFF_NN1_", "$_DFF_NP0_", "$_DFF_NP1_",
+						"$_DFF_P_", "$_DFF_PN0_", "$_DFF_PN1", "$_DFF_PP0_", "$_DFF_PP1_"))
+				continue;
+			SigBit D = cell->getPort("\\D");
+			SigBit Q = cell->getPort("\\Q");
+			// Emulate async control embedded inside $_DFF_* cell with mux in front of D
+			if (cell->type.in("$_DFF_NN0_", "$_DFF_PN0_"))
+				D = holes_module->MuxGate(NEW_ID, State::S0, D, cell->getPort("\\R"));
+			else if (cell->type.in("$_DFF_NN1_", "$_DFF_PN1_"))
+				D = holes_module->MuxGate(NEW_ID, State::S1, D, cell->getPort("\\R"));
+			else if (cell->type.in("$_DFF_NP0_", "$_DFF_PP0_"))
+				D = holes_module->MuxGate(NEW_ID, D, State::S0, cell->getPort("\\R"));
+			else if (cell->type.in("$_DFF_NP1_", "$_DFF_PP1_"))
+				D = holes_module->MuxGate(NEW_ID, D, State::S1, cell->getPort("\\R"));
+			// Remove the $_DFF_* cell from what needs to be a combinatorial box
+			holes_module->remove(cell);
+			Wire *port;
+			if (GetSize(Q.wire) == 1)
+				port = holes_module->wire(stringf("$abc%s", Q.wire->name.c_str()));
+			else
+				port = holes_module->wire(stringf("$abc%s[%d]", Q.wire->name.c_str(), Q.offset));
+			log_assert(port);
+			// Prepare to replace "assign <port> = $_DFF_*.Q;" with "assign <port> = $_DFF_*.D;"
+			//   in order to extract just the combinatorial control logic that feeds the box
+			//   (i.e. clock enable, synchronous reset, etc.)
+			replace.insert(std::make_pair(Q,D));
+			// Since `flatten` above would have created wires named "<cell>.Q",
+			//   extract the pre-techmap cell name
+			auto pos = Q.wire->name.str().rfind(".");
+			log_assert(pos != std::string::npos);
+			IdString driver = Q.wire->name.substr(0, pos);
+			// And drive the signal that was previously driven by "DFF.Q" (typically
+			//   used to implement clock-enable functionality) with the "<cell>.$abc9_currQ"
+			//   wire (which itself is driven an by input port) we inserted above
+			Wire *currQ = holes_module->wire(stringf("%s.abc9_ff.Q", driver.c_str()));
+			log_assert(currQ);
+			holes_module->connect(Q, currQ);
+		}
+
+		for (auto &conn : holes_module->connections_)
+			conn.second = replace.at(sigmap(conn.second), conn.second);
+	}
+}
+
+void prep_xaiger(RTLIL::Module *module, bool dff)
+{
+	auto design = module->design;
+	log_assert(design);
+
+	SigMap sigmap(module);
+
+	dict<SigBit, pool<IdString>> bit_drivers, bit_users;
+	TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
+	dict<IdString, std::vector<IdString>> box_ports;
+
+	for (auto cell : module->cells()) {
+		if (cell->type == "$__ABC9_FF_")
+			continue;
+		if (cell->has_keep_attr())
+			continue;
+
+		auto inst_module = module->design->module(cell->type);
+		bool abc9_flop = inst_module && inst_module->get_bool_attribute("\\abc9_flop");
+		if (abc9_flop && !dff)
+			continue;
+
+		if ((inst_module && inst_module->attributes.count("\\abc9_box_id")) || abc9_flop) {
+			auto r = box_ports.insert(cell->type);
+			if (r.second) {
+				// Make carry in the last PI, and carry out the last PO
+				//   since ABC requires it this way
+				IdString carry_in, carry_out;
+				for (const auto &port_name : inst_module->ports) {
+					auto w = inst_module->wire(port_name);
+					log_assert(w);
+					if (w->get_bool_attribute("\\abc9_carry")) {
+						log_assert(w->port_input != w->port_output);
+						if (w->port_input)
+							carry_in = port_name;
+						else if (w->port_output)
+							carry_out = port_name;
+					}
+					else
+						r.first->second.push_back(port_name);
+				}
+				if (carry_in != IdString()) {
+					r.first->second.push_back(carry_in);
+					r.first->second.push_back(carry_out);
+				}
+			}
+		}
+		else if (!yosys_celltypes.cell_known(cell->type))
+			continue;
+
+		// TODO: Speed up toposort -- we care about box ordering only
+		for (auto conn : cell->connections()) {
+			if (cell->input(conn.first))
+				for (auto bit : sigmap(conn.second))
+					bit_users[bit].insert(cell->name);
+
+			if (cell->output(conn.first) && !abc9_flop)
+				for (auto bit : sigmap(conn.second))
+					bit_drivers[bit].insert(cell->name);
+		}
+		toposort.node(cell->name);
+	}
+
+	if (box_ports.empty())
+		return;
+
+	for (auto &it : bit_users)
+		if (bit_drivers.count(it.first))
+			for (auto driver_cell : bit_drivers.at(it.first))
+			for (auto user_cell : it.second)
+				toposort.edge(driver_cell, user_cell);
+
+	if (ys_debug(1))
+		toposort.analyze_loops = true;
+
+	bool no_loops YS_ATTRIBUTE(unused) = toposort.sort();
+
+	if (ys_debug(1)) {
+		unsigned i = 0;
+		for (auto &it : toposort.loops) {
+			log("  loop %d\n", i++);
+			for (auto cell_name : it) {
+				auto cell = module->cell(cell_name);
+				log_assert(cell);
+				log("\t%s (%s @ %s)\n", log_id(cell), log_id(cell->type), cell->get_src_attribute().c_str());
+			}
+		}
+	}
+
+	log_assert(no_loops);
+
+	RTLIL::Module *holes_module = design->addModule(stringf("%s$holes", module->name.c_str()));
+	log_assert(holes_module);
+	holes_module->set_bool_attribute("\\abc9_holes");
+
+	dict<IdString, Cell*> cell_cache;
+
+	int port_id = 1, box_count = 0;
+	for (auto cell_name : toposort.sorted) {
+		RTLIL::Cell *cell = module->cell(cell_name);
+		log_assert(cell);
+
+		RTLIL::Module* box_module = design->module(cell->type);
+		if (!box_module || (!box_module->attributes.count("\\abc9_box_id") && !box_module->get_bool_attribute("\\abc9_flop")))
+			continue;
+
+		cell->attributes["\\abc9_box_seq"] = box_count++;
+
+		IdString derived_type = box_module->derive(design, cell->parameters);
+		box_module = design->module(derived_type);
+
+		auto r = cell_cache.insert(derived_type);
+		auto &holes_cell = r.first->second;
+		if (r.second) {
+			if (box_module->has_processes())
+				Pass::call_on_module(design, box_module, "proc");
+
+			if (box_module->get_bool_attribute("\\whitebox")) {
+				holes_cell = holes_module->addCell(cell->name, derived_type);
+
+				if (box_module->has_processes())
+					Pass::call_on_module(design, box_module, "proc");
+
+				int box_inputs = 0;
+				for (auto port_name : box_ports.at(cell->type)) {
+					RTLIL::Wire *w = box_module->wire(port_name);
+					log_assert(w);
+					log_assert(!w->port_input || !w->port_output);
+					auto &conn = holes_cell->connections_[port_name];
+					if (w->port_input) {
+						for (int i = 0; i < GetSize(w); i++) {
+							box_inputs++;
+							RTLIL::Wire *holes_wire = holes_module->wire(stringf("\\i%d", box_inputs));
+							if (!holes_wire) {
+								holes_wire = holes_module->addWire(stringf("\\i%d", box_inputs));
+								holes_wire->port_input = true;
+								holes_wire->port_id = port_id++;
+								holes_module->ports.push_back(holes_wire->name);
+							}
+							conn.append(holes_wire);
+						}
+					}
+					else if (w->port_output)
+						conn = holes_module->addWire(stringf("%s.%s", derived_type.c_str(), log_id(port_name)), GetSize(w));
+				}
+
+				// For flops only, create an extra 1-bit input that drives a new wire
+				//   called "<cell>.abc9_ff.Q" that is used below
+				if (box_module->get_bool_attribute("\\abc9_flop")) {
+					box_inputs++;
+					Wire *holes_wire = holes_module->wire(stringf("\\i%d", box_inputs));
+					if (!holes_wire) {
+						holes_wire = holes_module->addWire(stringf("\\i%d", box_inputs));
+						holes_wire->port_input = true;
+						holes_wire->port_id = port_id++;
+						holes_module->ports.push_back(holes_wire->name);
+					}
+					Wire *Q = holes_module->addWire(stringf("%s.abc9_ff.Q", cell->name.c_str()));
+					holes_module->connect(Q, holes_wire);
+				}
+			}
+			else // box_module is a blackbox
+				log_assert(holes_cell == nullptr);
+		}
+
+		for (auto port_name : box_ports.at(cell->type)) {
+			RTLIL::Wire *w = box_module->wire(port_name);
+			log_assert(w);
+			if (!w->port_output)
+				continue;
+			Wire *holes_wire = holes_module->addWire(stringf("$abc%s.%s", cell->name.c_str(), log_id(port_name)), GetSize(w));
+			holes_wire->port_output = true;
+			holes_wire->port_id = port_id++;
+			holes_module->ports.push_back(holes_wire->name);
+			if (holes_cell) // whitebox
+				holes_module->connect(holes_wire, holes_cell->getPort(port_name));
+			else // blackbox
+				holes_module->connect(holes_wire, Const(State::S0, GetSize(w)));
+		}
+	}
+}
+
+void prep_delays(RTLIL::Design *design)
+{
+	std::set<int> delays;
+	pool<Module*> flops;
+	std::vector<Cell*> cells;
+	dict<IdString,dict<IdString,std::vector<int>>> requireds_cache;
+	for (auto module : design->selected_modules()) {
+		if (module->processes.size() > 0) {
+			log("Skipping module %s as it contains processes.\n", log_id(module));
+			continue;
+		}
+
+		cells.clear();
+		for (auto cell : module->cells()) {
+			if (cell->type.in(ID($_AND_), ID($_NOT_), ID($__ABC9_FF_), ID($__ABC9_DELAY)))
+				continue;
+
+			RTLIL::Module* inst_module = module->design->module(cell->type);
+			if (!inst_module)
+				continue;
+			if (!inst_module->get_blackbox_attribute())
+				continue;
+			if (inst_module->get_bool_attribute(ID(abc9_flop))) {
+				IdString derived_type = inst_module->derive(design, cell->parameters);
+				inst_module = design->module(derived_type);
+				log_assert(inst_module);
+				flops.insert(inst_module);
+				continue; // because all flop required times
+				          //   will be captured in the flop box
+			}
+			if (inst_module->attributes.count(ID(abc9_box_id)))
+				continue;
+			cells.emplace_back(cell);
+		}
+
+		delays.clear();
+		for (auto cell : cells) {
+			RTLIL::Module* inst_module = module->design->module(cell->type);
+			log_assert(inst_module);
+			auto &cell_requireds = requireds_cache[cell->type];
+			for (auto &conn : cell->connections_) {
+				auto port_wire = inst_module->wire(conn.first);
+				if (!port_wire->port_input)
+					continue;
+
+				auto r = cell_requireds.insert(conn.first);
+				auto &requireds = r.first->second;
+				if (r.second) {
+					auto it = port_wire->attributes.find("\\abc9_required");
+					if (it == port_wire->attributes.end())
+						continue;
+					if (it->second.flags == 0) {
+						int delay = it->second.as_int();
+						delays.insert(delay);
+						requireds.emplace_back(delay);
+					}
+					else
+						for (const auto &tok : split_tokens(it->second.decode_string())) {
+							int delay = atoi(tok.c_str());
+							delays.insert(delay);
+							requireds.push_back(delay);
+						}
+				}
+
+				if (requireds.empty())
+					continue;
+
+				SigSpec O = module->addWire(NEW_ID, GetSize(conn.second));
+				auto it = requireds.begin();
+				for (int i = 0; i < GetSize(conn.second); ++i) {
+#ifndef NDEBUG
+					if (ys_debug(1)) {
+						static std::set<std::pair<IdString,IdString>> seen;
+						if (seen.emplace(cell->type, conn.first).second) log("%s.%s abc9_required = %d\n", log_id(cell->type), log_id(conn.first), requireds[i]);
+					}
+#endif
+					auto box = module->addCell(NEW_ID, ID($__ABC9_DELAY));
+					box->setPort(ID(I), conn.second[i]);
+					box->setPort(ID(O), O[i]);
+					box->setParam(ID(DELAY), *it);
+					if (requireds.size() > 1)
+						it++;
+					conn.second[i] = O[i];
+				}
+			}
+		}
+
+		std::stringstream ss;
+		bool first = true;
+		for (auto d : delays) {
+			if (first)
+				first = false;
+			else
+				ss << " ";
+			ss << d;
+		}
+		module->attributes[ID(abc9_delays)] = ss.str();
+	}
+
+	int flops_id = ABC9_FLOPS_BASE_ID;
+	std::stringstream ss;
+	for (auto flop_module : flops) {
+		int num_inputs = 0, num_outputs = 0;
+		for (auto port_name : flop_module->ports) {
+			auto wire = flop_module->wire(port_name);
+			if (wire->port_input) num_inputs++;
+			if (wire->port_output) num_outputs++;
+		}
+		log_assert(num_outputs == 1);
+
+		auto r = flop_module->attributes.insert(ID(abc9_box_id));
+		if (r.second)
+			r.first->second = flops_id++;
+
+		ss << log_id(flop_module) << " " << r.first->second.as_int();
+		ss << " 1 " << num_inputs+1 << " " << num_outputs << std::endl;
+		bool first = true;
+		for (auto port_name : flop_module->ports) {
+			auto wire = flop_module->wire(port_name);
+			if (!wire->port_input)
+				continue;
+			if (first)
+				first = false;
+			else
+				ss << " ";
+			ss << wire->attributes.at("\\abc9_required", 0).as_int();
+		}
+		// Last input is 'abc9_ff.Q'
+		ss << " 0" << std::endl << std::endl;
+	}
+	design->scratchpad_set_string("abc9_ops.box.flops", ss.str());
+}
+
+void write_box(RTLIL::Module *module, const std::string &src, const std::string &dst) {
+	std::ofstream ofs(dst);
+	log_assert(ofs.is_open());
+
+	// Since ABC can only accept one box file, we have to copy
+	//   over the existing box file
+	if (src != "(null)") {
+		std::ifstream ifs(src);
+		ofs << ifs.rdbuf() << std::endl;
+		ifs.close();
+	}
+
+	ofs << module->design->scratchpad_get_string("abc9_ops.box.flops");
+
+	auto it = module->attributes.find(ID(abc9_delays));
+	if (it != module->attributes.end()) {
+		for (const auto &tok : split_tokens(it->second.decode_string())) {
+			int d = atoi(tok.c_str());
+			ofs << "$__ABC9_DELAY@" << d << " " << ABC9_DELAY_BASE_ID + d << " 0 1 1" << std::endl;
+			ofs << d << std::endl;
+		}
+		module->attributes.erase(it);
+	}
+
+	if (ofs.tellp() == 0)
+		ofs << "(dummy) 1 0 0 0";
+
+	ofs.close();
+}
+
+void reintegrate(RTLIL::Module *module)
+{
+	auto design = module->design;
+	log_assert(design);
+
+	map_autoidx = autoidx++;
+
+	RTLIL::Module *mapped_mod = design->module(stringf("%s$abc9", module->name.c_str()));
+	if (mapped_mod == NULL)
+		log_error("ABC output file does not contain a module `%s$abc'.\n", log_id(module));
+
+	for (auto w : mapped_mod->wires())
+		module->addWire(remap_name(w->name), GetSize(w));
+
+	dict<IdString,std::vector<IdString>> box_ports;
+
+	for (auto m : design->modules()) {
+		if (!m->attributes.count(ID(abc9_box_id)))
+			continue;
+
+		auto r = box_ports.insert(m->name);
+		if (!r.second)
+			continue;
+
+		// Make carry in the last PI, and carry out the last PO
+		//   since ABC requires it this way
+		IdString carry_in, carry_out;
+		for (const auto &port_name : m->ports) {
+			auto w = m->wire(port_name);
+			log_assert(w);
+			if (w->get_bool_attribute("\\abc9_carry")) {
+				log_assert(w->port_input != w->port_output);
+				if (w->port_input)
+					carry_in = port_name;
+				else if (w->port_output)
+					carry_out = port_name;
+			}
+			else
+				r.first->second.push_back(port_name);
+		}
+
+		if (carry_in != IdString()) {
+			r.first->second.push_back(carry_in);
+			r.first->second.push_back(carry_out);
+		}
+	}
+
+	std::vector<Cell*> boxes;
+	for (auto cell : module->cells().to_vector()) {
+		if (cell->has_keep_attr())
+			continue;
+		if (cell->type.in(ID($_AND_), ID($_NOT_), ID($__ABC9_FF_)))
+			module->remove(cell);
+		else if (cell->attributes.erase("\\abc9_box_seq"))
+			boxes.emplace_back(cell);
+	}
+
+	dict<SigBit, pool<IdString>> bit_drivers, bit_users;
+	TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
+	dict<RTLIL::Cell*,RTLIL::Cell*> not2drivers;
+	dict<SigBit, std::vector<RTLIL::Cell*>> bit2sinks;
+
+	std::map<IdString, int> cell_stats;
+	for (auto mapped_cell : mapped_mod->cells())
+	{
+		// TODO: Speed up toposort -- we care about NOT ordering only
+		toposort.node(mapped_cell->name);
+
+		if (mapped_cell->type == ID($_NOT_)) {
+			RTLIL::SigBit a_bit = mapped_cell->getPort(ID::A);
+			RTLIL::SigBit y_bit = mapped_cell->getPort(ID::Y);
+			bit_users[a_bit].insert(mapped_cell->name);
+			// Ignore inouts for topo ordering
+			if (y_bit.wire && !(y_bit.wire->port_input && y_bit.wire->port_output))
+				bit_drivers[y_bit].insert(mapped_cell->name);
+
+			if (!a_bit.wire) {
+				mapped_cell->setPort(ID::Y, module->addWire(NEW_ID));
+				RTLIL::Wire *wire = module->wire(remap_name(y_bit.wire->name));
+				log_assert(wire);
+				module->connect(RTLIL::SigBit(wire, y_bit.offset), State::S1);
+			}
+			else {
+				RTLIL::Cell* driver_lut = nullptr;
+				// ABC can return NOT gates that drive POs
+				if (!a_bit.wire->port_input) {
+					// If it's not a NOT gate that that comes from a PI directly,
+					// find the driver LUT and clone that to guarantee that we won't
+					// increase the max logic depth
+					// (TODO: Optimise by not cloning unless will increase depth)
+					RTLIL::IdString driver_name;
+					if (GetSize(a_bit.wire) == 1)
+						driver_name = stringf("$lut%s", a_bit.wire->name.c_str());
+					else
+						driver_name = stringf("$lut%s[%d]", a_bit.wire->name.c_str(), a_bit.offset);
+					driver_lut = mapped_mod->cell(driver_name);
+				}
+
+				if (!driver_lut) {
+					// If a driver couldn't be found (could be from PI or box CI)
+					// then implement using a LUT
+					RTLIL::Cell *cell = module->addLut(remap_name(stringf("$lut%s", mapped_cell->name.c_str())),
+							RTLIL::SigBit(module->wires_.at(remap_name(a_bit.wire->name)), a_bit.offset),
+							RTLIL::SigBit(module->wires_.at(remap_name(y_bit.wire->name)), y_bit.offset),
+							RTLIL::Const::from_string("01"));
+					bit2sinks[cell->getPort(ID::A)].push_back(cell);
+					cell_stats[ID($lut)]++;
+				}
+				else
+					not2drivers[mapped_cell] = driver_lut;
+			}
+			continue;
+		}
+
+		if (mapped_cell->type.in(ID($lut), ID($__ABC9_FF_))) {
+			RTLIL::Cell *cell = module->addCell(remap_name(mapped_cell->name), mapped_cell->type);
+			cell->parameters = mapped_cell->parameters;
+			cell->attributes = mapped_cell->attributes;
+
+			for (auto &mapped_conn : mapped_cell->connections()) {
+				RTLIL::SigSpec newsig;
+				for (auto c : mapped_conn.second.chunks()) {
+					if (c.width == 0)
+						continue;
+					//log_assert(c.width == 1);
+					if (c.wire)
+						c.wire = module->wires_.at(remap_name(c.wire->name));
+					newsig.append(c);
+				}
+				cell->setPort(mapped_conn.first, newsig);
+
+				if (cell->input(mapped_conn.first)) {
+					for (auto i : newsig)
+						bit2sinks[i].push_back(cell);
+					for (auto i : mapped_conn.second)
+						bit_users[i].insert(mapped_cell->name);
+				}
+				if (cell->output(mapped_conn.first))
+					for (auto i : mapped_conn.second)
+						// Ignore inouts for topo ordering
+						if (i.wire && !(i.wire->port_input && i.wire->port_output))
+							bit_drivers[i].insert(mapped_cell->name);
+			}
+		}
+		else {
+			RTLIL::Cell *existing_cell = module->cell(mapped_cell->name);
+			if (!existing_cell)
+				log_error("Cannot find existing box cell with name '%s' in original design.\n", log_id(mapped_cell));
+
+			if (existing_cell->type == ID($__ABC9_DELAY)) {
+				SigBit I = mapped_cell->getPort(ID(i));
+				SigBit O = mapped_cell->getPort(ID(o));
+				if (I.wire)
+					I.wire = module->wires_.at(remap_name(I.wire->name));
+				log_assert(O.wire);
+				O.wire = module->wires_.at(remap_name(O.wire->name));
+				module->connect(O, I);
+				continue;
+			}
+
+#ifndef NDEBUG
+			RTLIL::Module* box_module = design->module(existing_cell->type);
+			IdString derived_type = box_module->derive(design, existing_cell->parameters);
+			RTLIL::Module* derived_module = design->module(derived_type);
+			log_assert(derived_module);
+			log_assert(mapped_cell->type == stringf("$__boxid%d", derived_module->attributes.at("\\abc9_box_id").as_int()));
+#endif
+			mapped_cell->type = existing_cell->type;
+
+			RTLIL::Cell *cell = module->addCell(remap_name(mapped_cell->name), mapped_cell->type);
+			cell->parameters = existing_cell->parameters;
+			cell->attributes = existing_cell->attributes;
+			module->swap_names(cell, existing_cell);
+
+			auto jt = mapped_cell->connections_.find("\\i");
+			log_assert(jt != mapped_cell->connections_.end());
+			SigSpec inputs = std::move(jt->second);
+			mapped_cell->connections_.erase(jt);
+			jt = mapped_cell->connections_.find("\\o");
+			log_assert(jt != mapped_cell->connections_.end());
+			SigSpec outputs = std::move(jt->second);
+			mapped_cell->connections_.erase(jt);
+
+			auto abc9_flop = box_module->attributes.count("\\abc9_flop");
+			if (!abc9_flop) {
+				for (const auto &i : inputs)
+					bit_users[i].insert(mapped_cell->name);
+				for (const auto &i : outputs)
+					// Ignore inouts for topo ordering
+					if (i.wire && !(i.wire->port_input && i.wire->port_output))
+						bit_drivers[i].insert(mapped_cell->name);
+			}
+
+			int input_count = 0, output_count = 0;
+			for (const auto &port_name : box_ports.at(cell->type)) {
+				RTLIL::Wire *w = box_module->wire(port_name);
+				log_assert(w);
+
+				SigSpec sig;
+				if (w->port_input) {
+					sig = inputs.extract(input_count, GetSize(w));
+					input_count += GetSize(w);
+				}
+				if (w->port_output) {
+					sig = outputs.extract(output_count, GetSize(w));
+					output_count += GetSize(w);
+				}
+
+				SigSpec newsig;
+				for (auto c : sig.chunks()) {
+					if (c.width == 0)
+						continue;
+					//log_assert(c.width == 1);
+					if (c.wire)
+						c.wire = module->wires_.at(remap_name(c.wire->name));
+					newsig.append(c);
+				}
+				cell->setPort(port_name, newsig);
+
+				if (w->port_input && !abc9_flop)
+					for (const auto &i : newsig)
+						bit2sinks[i].push_back(cell);
+			}
+		}
+
+		cell_stats[mapped_cell->type]++;
+	}
+
+	for (auto cell : boxes)
+		module->remove(cell);
+
+	// Copy connections (and rename) from mapped_mod to module
+	for (auto conn : mapped_mod->connections()) {
+		if (!conn.first.is_fully_const()) {
+			auto chunks = conn.first.chunks();
+			for (auto &c : chunks)
+				c.wire = module->wires_.at(remap_name(c.wire->name));
+			conn.first = std::move(chunks);
+		}
+		if (!conn.second.is_fully_const()) {
+			auto chunks = conn.second.chunks();
+			for (auto &c : chunks)
+				if (c.wire)
+					c.wire = module->wires_.at(remap_name(c.wire->name));
+			conn.second = std::move(chunks);
+		}
+		module->connect(conn);
+	}
+
+	for (auto &it : cell_stats)
+		log("ABC RESULTS:   %15s cells: %8d\n", it.first.c_str(), it.second);
+	int in_wires = 0, out_wires = 0;
+
+	// Stitch in mapped_mod's inputs/outputs into module
+	for (auto port : mapped_mod->ports) {
+		RTLIL::Wire *mapped_wire = mapped_mod->wire(port);
+		RTLIL::Wire *wire = module->wire(port);
+		log_assert(wire);
+		if (wire->attributes.erase(ID(abc9_scc_id))) {
+			auto r YS_ATTRIBUTE(unused) = wire->attributes.erase(ID::keep);
+			log_assert(r);
+		}
+		RTLIL::Wire *remap_wire = module->wire(remap_name(port));
+		RTLIL::SigSpec signal(wire, 0, GetSize(remap_wire));
+		log_assert(GetSize(signal) >= GetSize(remap_wire));
+
+		RTLIL::SigSig conn;
+		if (mapped_wire->port_output) {
+			conn.first = signal;
+			conn.second = remap_wire;
+			out_wires++;
+			module->connect(conn);
+		}
+		else if (mapped_wire->port_input) {
+			conn.first = remap_wire;
+			conn.second = signal;
+			in_wires++;
+			module->connect(conn);
+		}
+	}
+
+	// ABC9 will return $_NOT_ gates in its mapping (since they are
+	//   treated as being "free"), in particular driving primary
+	//   outputs (real primary outputs, or cells treated as blackboxes)
+	//   or driving box inputs.
+	// Instead of just mapping those $_NOT_ gates into 2-input $lut-s
+	//   at an area and delay cost, see if it is possible to push
+	//   this $_NOT_ into the driving LUT, or into all sink LUTs.
+	// When this is not possible, (i.e. this signal drives two primary
+	//   outputs, only one of which is complemented) and when the driver
+	//   is a LUT, then clone the LUT so that it can be inverted without
+	//   increasing depth/delay.
+	for (auto &it : bit_users)
+		if (bit_drivers.count(it.first))
+			for (auto driver_cell : bit_drivers.at(it.first))
+			for (auto user_cell : it.second)
+				toposort.edge(driver_cell, user_cell);
+	bool no_loops YS_ATTRIBUTE(unused) = toposort.sort();
+	log_assert(no_loops);
+
+	for (auto ii = toposort.sorted.rbegin(); ii != toposort.sorted.rend(); ii++) {
+		RTLIL::Cell *not_cell = mapped_mod->cell(*ii);
+		log_assert(not_cell);
+		if (not_cell->type != ID($_NOT_))
+			continue;
+		auto it = not2drivers.find(not_cell);
+		if (it == not2drivers.end())
+			continue;
+		RTLIL::Cell *driver_lut = it->second;
+		RTLIL::SigBit a_bit = not_cell->getPort(ID::A);
+		RTLIL::SigBit y_bit = not_cell->getPort(ID::Y);
+		RTLIL::Const driver_mask;
+
+		a_bit.wire = module->wires_.at(remap_name(a_bit.wire->name));
+		y_bit.wire = module->wires_.at(remap_name(y_bit.wire->name));
+
+		auto jt = bit2sinks.find(a_bit);
+		if (jt == bit2sinks.end())
+			goto clone_lut;
+
+		for (auto sink_cell : jt->second)
+			if (sink_cell->type != ID($lut))
+				goto clone_lut;
+
+		// Push downstream LUTs past inverter
+		for (auto sink_cell : jt->second) {
+			SigSpec A = sink_cell->getPort(ID::A);
+			RTLIL::Const mask = sink_cell->getParam(ID(LUT));
+			int index = 0;
+			for (; index < GetSize(A); index++)
+				if (A[index] == a_bit)
+					break;
+			log_assert(index < GetSize(A));
+			int i = 0;
+			while (i < GetSize(mask)) {
+				for (int j = 0; j < (1 << index); j++)
+					std::swap(mask[i+j], mask[i+j+(1 << index)]);
+				i += 1 << (index+1);
+			}
+			A[index] = y_bit;
+			sink_cell->setPort(ID::A, A);
+			sink_cell->setParam(ID(LUT), mask);
+		}
+
+		// Since we have rewritten all sinks (which we know
+		// to be only LUTs) to be after the inverter, we can
+		// go ahead and clone the LUT with the expectation
+		// that the original driving LUT will become dangling
+		// and get cleaned away
+clone_lut:
+		driver_mask = driver_lut->getParam(ID(LUT));
+		for (auto &b : driver_mask.bits) {
+			if (b == RTLIL::State::S0) b = RTLIL::State::S1;
+			else if (b == RTLIL::State::S1) b = RTLIL::State::S0;
+		}
+		auto cell = module->addLut(NEW_ID,
+				driver_lut->getPort(ID::A),
+				y_bit,
+				driver_mask);
+		for (auto &bit : cell->connections_.at(ID::A)) {
+			bit.wire = module->wires_.at(remap_name(bit.wire->name));
+			bit2sinks[bit].push_back(cell);
+		}
+	}
+
+	//log("ABC RESULTS:        internal signals: %8d\n", int(signal_list.size()) - in_wires - out_wires);
+	log("ABC RESULTS:           input signals: %8d\n", in_wires);
+	log("ABC RESULTS:          output signals: %8d\n", out_wires);
+
+	design->remove(mapped_mod);
+}
+
+struct Abc9OpsPass : public Pass {
+	Abc9OpsPass() : Pass("abc9_ops", "helper functions for ABC9") { }
+	void help() YS_OVERRIDE
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    abc9_ops [options] [selection]\n");
+		log("\n");
+		log("This pass contains a set of supporting operations for use during ABC technology\n");
+		log("mapping, and is expected to be called in conjunction with other operations from\n");
+		log("the `abc9' script pass. Only fully-selected modules are supported.\n");
+		log("\n");
+		log("    -check\n");
+		log("        check that the design is valid, e.g. (* abc9_box_id *) values are unique,\n");
+		log("        (* abc9_carry *) is only given for one input/output port, etc.\n");
+		log("\n");
+		log("    -prep_delays\n");
+		log("        insert `$__ABC9_DELAY' blackbox cells into the design to account for\n");
+		log("        certain delays, e.g. (* abc9_required *) values.\n");
+		log("\n");
+		log("    -mark_scc\n");
+		log("        for an arbitrarily chosen cell in each unique SCC of each selected module\n");
+		log("        (tagged with an (* abc9_scc_id = <int> *) attribute), temporarily mark all\n");
+		log("        wires driven by this cell's outputs with a (* keep *) attribute in order\n");
+		log("        to break the SCC. this temporary attribute will be removed on -reintegrate.\n");
+		log("\n");
+		log("    -prep_xaiger\n");
+		log("        prepare the design for XAIGER output. this includes computing the\n");
+		log("        topological ordering of ABC9 boxes, as well as preparing the\n");
+		log("        '<module-name>$holes' module that contains the logic behaviour of ABC9\n");
+		log("        whiteboxes.\n");
+		log("\n");
+		log("    -dff\n");
+		log("        consider flop cells (those instantiating modules marked with (* abc9_flop *)\n");
+		log("        during -prep_xaiger.\n");
+		log("\n");
+		log("    -prep_dff\n");
+		log("        compute the clock domain and initial value of each flop in the design.\n");
+		log("        process the '$holes' module to support clock-enable functionality.\n");
+		log("\n");
+		log("    -write_box (<src>|(null)) <dst>\n");
+		log("        copy the existing box file from <src> (skip if '(null)') and append any\n");
+		log("        new box definitions.\n");
+		log("\n");
+		log("    -reintegrate\n");
+		log("        for each selected module, re-intergrate the module '<module-name>$abc9'\n");
+		log("        by first recovering ABC9 boxes, and then stitching in the remaining primary\n");
+		log("        inputs and outputs.\n");
+		log("\n");
+	}
+	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+	{
+		log_header(design, "Executing ABC9_OPS pass (helper functions for ABC9).\n");
+
+		bool check_mode = false;
+		bool prep_delays_mode = false;
+		bool mark_scc_mode = false;
+		bool prep_dff_mode = false;
+		bool prep_xaiger_mode = false;
+		bool reintegrate_mode = false;
+		bool dff_mode = false;
+		std::string write_box_src, write_box_dst;
+
+		size_t argidx;
+		for (argidx = 1; argidx < args.size(); argidx++) {
+			std::string arg = args[argidx];
+			if (arg == "-check") {
+				check_mode = true;
+				continue;
+			}
+			if (arg == "-mark_scc") {
+				mark_scc_mode = true;
+				continue;
+			}
+			if (arg == "-prep_dff") {
+				prep_dff_mode = true;
+				continue;
+			}
+			if (arg == "-prep_xaiger") {
+				prep_xaiger_mode = true;
+				continue;
+			}
+			if (arg == "-prep_delays") {
+				prep_delays_mode = true;
+				continue;
+			}
+			if (arg == "-write_box" && argidx+2 < args.size()) {
+				write_box_src = args[++argidx];
+				write_box_dst = args[++argidx];
+				rewrite_filename(write_box_src);
+				rewrite_filename(write_box_dst);
+				continue;
+			}
+			if (arg == "-reintegrate") {
+				reintegrate_mode = true;
+				continue;
+			}
+			if (arg == "-dff") {
+				dff_mode = true;
+				continue;
+			}
+			break;
+		}
+		extra_args(args, argidx, design);
+
+		if (!(check_mode || mark_scc_mode || prep_delays_mode || prep_xaiger_mode || prep_dff_mode || !write_box_src.empty() || reintegrate_mode))
+			log_cmd_error("At least one of -check, -mark_scc, -prep_{delays,xaiger,dff}, -write_box, -reintegrate must be specified.\n");
+
+		if (dff_mode && !prep_xaiger_mode)
+			log_cmd_error("'-dff' option is only relevant for -prep_xaiger.\n");
+
+		if (check_mode)
+			check(design);
+		if (prep_delays_mode)
+			prep_delays(design);
+
+		for (auto mod : design->selected_modules()) {
+			if (mod->get_bool_attribute("\\abc9_holes"))
+				continue;
+
+			if (mod->processes.size() > 0) {
+				log("Skipping module %s as it contains processes.\n", log_id(mod));
+				continue;
+			}
+
+			if (!design->selected_whole_module(mod))
+				log_error("Can't handle partially selected module %s!\n", log_id(mod));
+
+			if (!write_box_src.empty())
+				write_box(mod, write_box_src, write_box_dst);
+			if (mark_scc_mode)
+				mark_scc(mod);
+			if (prep_dff_mode)
+				prep_dff(mod);
+			if (prep_xaiger_mode)
+				prep_xaiger(mod, dff_mode);
+			if (reintegrate_mode)
+				reintegrate(mod);
+		}
+	}
+} Abc9OpsPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/tribuf.cc b/passes/techmap/tribuf.cc
index 41fdc8f3d..decf9a202 100644
--- a/passes/techmap/tribuf.cc
+++ b/passes/techmap/tribuf.cc
@@ -86,6 +86,7 @@ struct TribufWorker {
 					cell->unsetPort(ID(S));
 					cell->type = tri_type;
 					tribuf_cells[sigmap(cell->getPort(ID::Y))].push_back(cell);
+					module->design->scratchpad_set_bool("tribuf.added_something", true);
 					continue;
 				}
 
@@ -95,6 +96,7 @@ struct TribufWorker {
 					cell->unsetPort(ID(S));
 					cell->type = tri_type;
 					tribuf_cells[sigmap(cell->getPort(ID::Y))].push_back(cell);
+					module->design->scratchpad_set_bool("tribuf.added_something", true);
 					continue;
 				}
 			}
@@ -130,8 +132,10 @@ struct TribufWorker {
 
 				if (no_tribuf)
 					module->connect(it.first, muxout);
-				else
+				else {
 					module->addTribuf(NEW_ID, muxout, module->ReduceOr(NEW_ID, pmux_s), it.first);
+					module->design->scratchpad_set_bool("tribuf.added_something", true);
+				}
 			}
 		}
 	}
diff --git a/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc
index a42f63128..42f1068ad 100644
--- a/techlibs/common/Makefile.inc
+++ b/techlibs/common/Makefile.inc
@@ -29,4 +29,3 @@ $(eval $(call add_share_file,share,techlibs/common/gate2lut.v))
 $(eval $(call add_share_file,share,techlibs/common/cmp2lut.v))
 $(eval $(call add_share_file,share,techlibs/common/cells.lib))
 $(eval $(call add_share_file,share,techlibs/common/mul2dsp.v))
-$(eval $(call add_share_file,share,techlibs/common/dummy.box))
diff --git a/techlibs/common/dummy.box b/techlibs/common/dummy.box
deleted file mode 100644
index 0c18070a0..000000000
--- a/techlibs/common/dummy.box
+++ /dev/null
@@ -1 +0,0 @@
-(dummy) 1 0 0 0
diff --git a/techlibs/ecp5/cells_map.v b/techlibs/ecp5/cells_map.v
index 10e89a3e0..c031703a9 100644
--- a/techlibs/ecp5/cells_map.v
+++ b/techlibs/ecp5/cells_map.v
@@ -73,102 +73,80 @@ module \$lut (A, Y);
     input [WIDTH-1:0] A;
     output Y;
 
-    // Need to swap input ordering, and fix init accordingly,
-    // to match ABC's expectation of LUT inputs in non-decreasing
-    // delay order
-    localparam P_WIDTH = WIDTH < 4 ? 4 : WIDTH;
-    function [P_WIDTH-1:0] permute_index;
-        input [P_WIDTH-1:0] i;
-        integer j;
-        begin
-            permute_index = 0;
-            for (j = 0; j < P_WIDTH; j = j + 1)
-                permute_index[P_WIDTH-1 - j] = i[j];
-        end
-    endfunction
-
-    function [2**P_WIDTH-1:0] permute_init;
-        integer i;
-        begin
-            permute_init = 0;
-            for (i = 0; i < 2**P_WIDTH; i = i + 1)
-                permute_init[i] = LUT[permute_index(i)];
-        end
-    endfunction
-
-    parameter [2**P_WIDTH-1:0] P_LUT = permute_init();
-
     generate
         if (WIDTH == 1) begin
-            LUT4 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.Z(Y),
+            localparam [15:0] INIT = {{8{LUT[1]}}, {8{LUT[0]}}};
+            LUT4 #(.INIT(INIT)) _TECHMAP_REPLACE_ (.Z(Y),
                 .A(1'b0), .B(1'b0), .C(1'b0), .D(A[0]));
         end else
         if (WIDTH == 2) begin
-            LUT4 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.Z(Y),
-                .A(1'b0), .B(1'b0), .C(A[1]), .D(A[0]));
+            localparam [15:0] INIT = {{4{LUT[3]}}, {4{LUT[2]}}, {4{LUT[1]}}, {4{LUT[0]}}};
+            LUT4 #(.INIT(INIT)) _TECHMAP_REPLACE_ (.Z(Y),
+                .A(1'b0), .B(1'b0), .C(A[0]), .D(A[1]));
         end else
         if (WIDTH == 3) begin
-            LUT4 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.Z(Y),
-                .A(1'b0), .B(A[2]), .C(A[1]), .D(A[0]));
+            localparam [15:0] INIT = {{2{LUT[7]}}, {2{LUT[6]}}, {2{LUT[5]}}, {2{LUT[4]}}, {2{LUT[3]}}, {2{LUT[2]}}, {2{LUT[1]}}, {2{LUT[0]}}};
+            LUT4 #(.INIT(INIT)) _TECHMAP_REPLACE_ (.Z(Y),
+                .A(1'b0), .B(A[0]), .C(A[1]), .D(A[2]));
         end else
         if (WIDTH == 4) begin
-            LUT4 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.Z(Y),
-                .A(A[3]), .B(A[2]), .C(A[1]), .D(A[0]));
+            LUT4 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.Z(Y),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
         `ifndef NO_PFUMUX
         end else
         if (WIDTH == 5) begin
             wire f0, f1;
-            LUT4 #(.INIT(P_LUT[15: 0])) lut0 (.Z(f0),
-                .A(A[4]), .B(A[3]), .C(A[2]), .D(A[1]));
-            LUT4 #(.INIT(P_LUT[31:16])) lut1 (.Z(f1),
-                .A(A[4]), .B(A[3]), .C(A[2]), .D(A[1]));
-            PFUMX mux5(.ALUT(f1), .BLUT(f0), .C0(A[0]), .Z(Y));
+            LUT4 #(.INIT(LUT[15: 0])) lut0 (.Z(f0),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[31:16])) lut1 (.Z(f1),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            PFUMX mux5(.ALUT(f1), .BLUT(f0), .C0(A[4]), .Z(Y));
         end else
         if (WIDTH == 6) begin
             wire f0, f1, f2, f3, g0, g1;
-            LUT4 #(.INIT(P_LUT[15: 0])) lut0 (.Z(f0),
-                .A(A[5]), .B(A[4]), .C(A[3]), .D(A[2]));
-            LUT4 #(.INIT(P_LUT[31:16])) lut1 (.Z(f1),
-                .A(A[5]), .B(A[4]), .C(A[3]), .D(A[2]));
+            LUT4 #(.INIT(LUT[15: 0])) lut0 (.Z(f0),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[31:16])) lut1 (.Z(f1),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            LUT4 #(.INIT(P_LUT[47:32])) lut2 (.Z(f2),
-                .A(A[5]), .B(A[4]), .C(A[3]), .D(A[2]));
-            LUT4 #(.INIT(P_LUT[63:48])) lut3 (.Z(f3),
-                .A(A[5]), .B(A[4]), .C(A[3]), .D(A[2]));
+            LUT4 #(.INIT(LUT[47:32])) lut2 (.Z(f2),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[63:48])) lut3 (.Z(f3),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            PFUMX mux50(.ALUT(f1), .BLUT(f0), .C0(A[1]), .Z(g0));
-            PFUMX mux51(.ALUT(f3), .BLUT(f2), .C0(A[1]), .Z(g1));
-            L6MUX21 mux6 (.D0(g0), .D1(g1), .SD(A[0]), .Z(Y));
+            PFUMX mux50(.ALUT(f1), .BLUT(f0), .C0(A[4]), .Z(g0));
+            PFUMX mux51(.ALUT(f3), .BLUT(f2), .C0(A[4]), .Z(g1));
+            L6MUX21 mux6 (.D0(g0), .D1(g1), .SD(A[5]), .Z(Y));
         end else
         if (WIDTH == 7) begin
             wire f0, f1, f2, f3, f4, f5, f6, f7, g0, g1, g2, g3, h0, h1;
-            LUT4 #(.INIT(P_LUT[15: 0])) lut0 (.Z(f0),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
-            LUT4 #(.INIT(P_LUT[31:16])) lut1 (.Z(f1),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
+            LUT4 #(.INIT(LUT[15: 0])) lut0 (.Z(f0),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[31:16])) lut1 (.Z(f1),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            LUT4 #(.INIT(P_LUT[47:32])) lut2 (.Z(f2),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
-            LUT4 #(.INIT(P_LUT[63:48])) lut3 (.Z(f3),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
+            LUT4 #(.INIT(LUT[47:32])) lut2 (.Z(f2),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[63:48])) lut3 (.Z(f3),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            LUT4 #(.INIT(P_LUT[79:64])) lut4 (.Z(f4),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
-            LUT4 #(.INIT(P_LUT[95:80])) lut5 (.Z(f5),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
+            LUT4 #(.INIT(LUT[79:64])) lut4 (.Z(f4),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[95:80])) lut5 (.Z(f5),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            LUT4 #(.INIT(P_LUT[111: 96])) lut6 (.Z(f6),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
-            LUT4 #(.INIT(P_LUT[127:112])) lut7 (.Z(f7),
-                .A(A[6]), .B(A[5]), .C(A[4]), .D(A[3]));
+            LUT4 #(.INIT(LUT[111: 96])) lut6 (.Z(f6),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
+            LUT4 #(.INIT(LUT[127:112])) lut7 (.Z(f7),
+                .A(A[0]), .B(A[1]), .C(A[2]), .D(A[3]));
 
-            PFUMX mux50(.ALUT(f1), .BLUT(f0), .C0(A[2]), .Z(g0));
-            PFUMX mux51(.ALUT(f3), .BLUT(f2), .C0(A[2]), .Z(g1));
-            PFUMX mux52(.ALUT(f5), .BLUT(f4), .C0(A[2]), .Z(g2));
-            PFUMX mux53(.ALUT(f7), .BLUT(f6), .C0(A[2]), .Z(g3));
-            L6MUX21 mux60 (.D0(g0), .D1(g1), .SD(A[1]), .Z(h0));
-            L6MUX21 mux61 (.D0(g2), .D1(g3), .SD(A[1]), .Z(h1));
-            L6MUX21 mux7  (.D0(h0), .D1(h1), .SD(A[0]), .Z(Y));
+            PFUMX mux50(.ALUT(f1), .BLUT(f0), .C0(A[4]), .Z(g0));
+            PFUMX mux51(.ALUT(f3), .BLUT(f2), .C0(A[4]), .Z(g1));
+            PFUMX mux52(.ALUT(f5), .BLUT(f4), .C0(A[4]), .Z(g2));
+            PFUMX mux53(.ALUT(f7), .BLUT(f6), .C0(A[4]), .Z(g3));
+            L6MUX21 mux60 (.D0(g0), .D1(g1), .SD(A[5]), .Z(h0));
+            L6MUX21 mux61 (.D0(g2), .D1(g3), .SD(A[5]), .Z(h1));
+            L6MUX21 mux7  (.D0(h0), .D1(h1), .SD(A[6]), .Z(Y));
         `endif
         end else begin
             wire _TECHMAP_FAIL_ = 1;
diff --git a/techlibs/ecp5/synth_ecp5.cc b/techlibs/ecp5/synth_ecp5.cc
index 6583f43fd..bce20f604 100644
--- a/techlibs/ecp5/synth_ecp5.cc
+++ b/techlibs/ecp5/synth_ecp5.cc
@@ -343,6 +343,7 @@ struct SynthEcp5Pass : public ScriptPass
 			else
 				run("techmap -map +/ecp5/cells_map.v", "(with -D NO_LUT in vpr mode)");
 
+			run("opt_lut_ins -tech ecp5");
 			run("clean");
 		}
 
diff --git a/techlibs/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc
index c5b41b503..99dd3834b 100644
--- a/techlibs/gowin/synth_gowin.cc
+++ b/techlibs/gowin/synth_gowin.cc
@@ -246,6 +246,7 @@ struct SynthGowinPass : public ScriptPass
 		if (check_label("map_cells"))
 		{
 			run("techmap -map +/gowin/cells_map.v");
+			run("opt_lut_ins -tech gowin");
 			run("setundef -undriven -params -zero");
 			run("hilomap -singleton -hicell VCC V -locell GND G");
 			if (!noiopads || help_mode)
diff --git a/techlibs/ice40/abc9_model.v b/techlibs/ice40/abc9_model.v
index 26cf6cc22..a5e5f4372 100644
--- a/techlibs/ice40/abc9_model.v
+++ b/techlibs/ice40/abc9_model.v
@@ -9,6 +9,8 @@ module \$__ICE40_CARRY_WRAPPER (
 	input I0, I3
 );
 	parameter LUT = 0;
+	parameter I3_IS_CI = 0;
+	wire I3_OR_CI = I3_IS_CI ? CI : I3;
 	SB_CARRY carry (
 		.I0(A),
 		.I1(B),
@@ -21,7 +23,7 @@ module \$__ICE40_CARRY_WRAPPER (
 		.I0(I0),
 		.I1(A),
 		.I2(B),
-		.I3(I3),
+		.I3(I3_OR_CI),
 		.O(O)
 	);
 endmodule
diff --git a/techlibs/ice40/abc9_u.box b/techlibs/ice40/abc9_u.box
index 48a51463e..3d4b93834 100644
--- a/techlibs/ice40/abc9_u.box
+++ b/techlibs/ice40/abc9_u.box
@@ -6,13 +6,12 @@
 
 # Box 1 : $__ICE40_CARRY_WRAPPER (private cell used to preserve
 #                                 SB_LUT4+SB_CARRY)
-# Outputs: O, CO
 #   (Exception: carry chain input/output must be the
 #        last input and output and the entire bus has been
 #        moved there overriding the otherwise
 #        alphabetical ordering)
 # name                 ID  w/b ins outs
 $__ICE40_CARRY_WRAPPER 1   1   5   2
-#A  B   I0  I3  CI
+#A   B    I0   I3  CI
 1231 1205 1285 874 874 # O
 675  609  -    -   278 # CO
diff --git a/techlibs/ice40/arith_map.v b/techlibs/ice40/arith_map.v
index 00a07247b..ed4140e44 100644
--- a/techlibs/ice40/arith_map.v
+++ b/techlibs/ice40/arith_map.v
@@ -49,13 +49,14 @@ module _80_ice40_alu (A, B, CI, BI, X, Y, CO);
 			//    A[1]: 1100 1100 1100 1100
 			//    A[2]: 1111 0000 1111 0000
 			//    A[3]: 1111 1111 0000 0000
-			.LUT(16'b 0110_1001_1001_0110)
+			.LUT(16'b 0110_1001_1001_0110),
+			.I3_IS_CI(1'b1)
 		) carry (
 			.A(AA[i]),
 			.B(BB[i]),
 			.CI(C[i]),
 			.I0(1'b0),
-			.I3(C[i]),
+			.I3(1'bx),
 			.CO(CO[i]),
 			.O(Y[i])
 		);
diff --git a/techlibs/ice40/cells_map.v b/techlibs/ice40/cells_map.v
index 759549e30..d5362eb83 100644
--- a/techlibs/ice40/cells_map.v
+++ b/techlibs/ice40/cells_map.v
@@ -42,19 +42,18 @@ module \$lut (A, Y);
         .I0(1'b0), .I1(1'b0), .I2(1'b0), .I3(A[0]));
     end else
     if (WIDTH == 2) begin
-      localparam [15:0] INIT = {{4{LUT[3]}}, {4{LUT[1]}}, {4{LUT[2]}}, {4{LUT[0]}}};
+      localparam [15:0] INIT = {{4{LUT[3]}}, {4{LUT[2]}}, {4{LUT[1]}}, {4{LUT[0]}}};
       SB_LUT4 #(.LUT_INIT(INIT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(1'b0), .I1(1'b0), .I2(A[1]), .I3(A[0]));
+        .I0(1'b0), .I1(1'b0), .I2(A[0]), .I3(A[1]));
     end else
     if (WIDTH == 3) begin
-      localparam [15:0] INIT = {{2{LUT[7]}}, {2{LUT[3]}}, {2{LUT[5]}}, {2{LUT[1]}}, {2{LUT[6]}}, {2{LUT[2]}}, {2{LUT[4]}}, {2{LUT[0]}}};
+      localparam [15:0] INIT = {{2{LUT[7]}}, {2{LUT[6]}}, {2{LUT[5]}}, {2{LUT[4]}}, {2{LUT[3]}}, {2{LUT[2]}}, {2{LUT[1]}}, {2{LUT[0]}}};
       SB_LUT4 #(.LUT_INIT(INIT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(1'b0), .I1(A[2]), .I2(A[1]), .I3(A[0]));
+        .I0(1'b0), .I1(A[0]), .I2(A[1]), .I3(A[2]));
     end else
     if (WIDTH == 4) begin
-      localparam [15:0] INIT = {LUT[15], LUT[7], LUT[11], LUT[3], LUT[13], LUT[5], LUT[9], LUT[1], LUT[14], LUT[6], LUT[10], LUT[2], LUT[12], LUT[4], LUT[8], LUT[0]};
-      SB_LUT4 #(.LUT_INIT(INIT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[3]), .I1(A[2]), .I2(A[1]), .I3(A[0]));
+      SB_LUT4 #(.LUT_INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]), .I3(A[3]));
     end else begin
       wire _TECHMAP_FAIL_ = 1;
     end
diff --git a/techlibs/ice40/cells_sim.v b/techlibs/ice40/cells_sim.v
index 7d1b37fd6..50eab5dde 100644
--- a/techlibs/ice40/cells_sim.v
+++ b/techlibs/ice40/cells_sim.v
@@ -1126,6 +1126,7 @@ module SB_SPRAM256KA (
 	input [15:0] DATAIN,
 	input [3:0] MASKWREN,
 	input WREN, CHIPSELECT, CLOCK, STANDBY, SLEEP, POWEROFF,
+	`ABC9_ARRIVAL_U(1821)  // https://github.com/cliffordwolf/icestorm/blob/95949315364f8d9b0c693386aefadf44b28e2cf6/icefuzz/timings_up5k.txt#L13207
 	output reg [15:0] DATAOUT
 );
 `ifndef BLACKBOX
diff --git a/techlibs/ice40/ice40_opt.cc b/techlibs/ice40/ice40_opt.cc
index 9bee0444b..925ab31bb 100644
--- a/techlibs/ice40/ice40_opt.cc
+++ b/techlibs/ice40/ice40_opt.cc
@@ -139,7 +139,8 @@ static void run_ice40_opts(Module *module)
 				log("Optimized $__ICE40_CARRY_WRAPPER cell back to logic (without SB_CARRY) %s.%s: CO=%s\n",
 						log_id(module), log_id(cell), log_signal(replacement_output));
 				cell->type = "$lut";
-				cell->setPort("\\A", { cell->getPort("\\I0"), inbit[0], inbit[1], cell->getPort("\\I3") });
+				auto I3 = get_bit_or_zero(cell->getPort(cell->getParam(ID(I3_IS_CI)).as_bool() ? ID(CI) : ID(I3)));
+				cell->setPort("\\A", { I3, inbit[1], inbit[0], get_bit_or_zero(cell->getPort("\\I0")) });
 				cell->setPort("\\Y", cell->getPort("\\O"));
 				cell->unsetPort("\\B");
 				cell->unsetPort("\\CI");
@@ -148,6 +149,7 @@ static void run_ice40_opts(Module *module)
 				cell->unsetPort("\\CO");
 				cell->unsetPort("\\O");
 				cell->setParam("\\WIDTH", 4);
+				cell->unsetParam("\\I3_IS_CI");
 			}
 			continue;
 		}
diff --git a/techlibs/intel/synth_intel.cc b/techlibs/intel/synth_intel.cc
index 2ebb8bf50..3689df70e 100644
--- a/techlibs/intel/synth_intel.cc
+++ b/techlibs/intel/synth_intel.cc
@@ -26,7 +26,7 @@ USING_YOSYS_NAMESPACE
 PRIVATE_NAMESPACE_BEGIN
 
 struct SynthIntelPass : public ScriptPass {
-	SynthIntelPass() : ScriptPass("synth_intel", "synthesis for Intel (Altera) FPGAs.") {}
+	SynthIntelPass() : ScriptPass("synth_intel", "synthesis for Intel (Altera) FPGAs.") { experimental(); }
 
 	void help() YS_OVERRIDE
 	{
diff --git a/techlibs/xilinx/abc9_map.v b/techlibs/xilinx/abc9_map.v
index 0652064cb..7dc027176 100644
--- a/techlibs/xilinx/abc9_map.v
+++ b/techlibs/xilinx/abc9_map.v
@@ -74,7 +74,7 @@
 // (e) a special _TECHMAP_REPLACE_.abc9_ff.Q wire that will be used for feedback
 //     into the (combinatorial) FD* cell to facilitate clock-enable behaviour
 
-module FDRE (output Q, input C, CE, D, R);
+module FDRE (output Q, (* techmap_autopurge *) input C, CE, D, R);
   parameter [0:0] INIT = 1'b0;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -110,7 +110,7 @@ module FDRE (output Q, input C, CE, D, R);
   wire [0:0] abc9_ff.init = 1'b0;
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = QQ;
 endmodule
-module FDRE_1 (output Q, input C, CE, D, R);
+module FDRE_1 (output Q, (* techmap_autopurge *) input C, CE, D, R);
   parameter [0:0] INIT = 1'b0;
   wire QQ, $Q;
   generate if (INIT == 1'b1) begin
@@ -138,7 +138,7 @@ module FDRE_1 (output Q, input C, CE, D, R);
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = QQ;
 endmodule
 
-module FDSE (output Q, input C, CE, D, S);
+module FDSE (output Q, (* techmap_autopurge *) input C, CE, D, S);
   parameter [0:0] INIT = 1'b1;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -173,7 +173,7 @@ module FDSE (output Q, input C, CE, D, S);
   wire [0:0] abc9_ff.init = 1'b0;
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = QQ;
 endmodule
-module FDSE_1 (output Q, input C, CE, D, S);
+module FDSE_1 (output Q, (* techmap_autopurge *) input C, CE, D, S);
   parameter [0:0] INIT = 1'b1;
   wire QQ, $Q;
   generate if (INIT == 1'b1) begin
@@ -200,7 +200,7 @@ module FDSE_1 (output Q, input C, CE, D, S);
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = QQ;
 endmodule
 
-module FDCE (output Q, input C, CE, D, CLR);
+module FDCE (output Q, (* techmap_autopurge *) input C, CE, D, CLR);
   parameter [0:0] INIT = 1'b0;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -249,7 +249,7 @@ module FDCE (output Q, input C, CE, D, CLR);
   wire [0:0] abc9_ff.init = 1'b0;
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = $QQ;
 endmodule
-module FDCE_1 (output Q, input C, CE, D, CLR);
+module FDCE_1 (output Q, (* techmap_autopurge *) input C, CE, D, CLR);
   parameter [0:0] INIT = 1'b0;
   wire QQ, $Q, $QQ;
   generate if (INIT == 1'b1) begin
@@ -288,7 +288,7 @@ module FDCE_1 (output Q, input C, CE, D, CLR);
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = $QQ;
 endmodule
 
-module FDPE (output Q, input C, CE, D, PRE);
+module FDPE (output Q, (* techmap_autopurge *) input C, CE, D, PRE);
   parameter [0:0] INIT = 1'b1;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -335,7 +335,7 @@ module FDPE (output Q, input C, CE, D, PRE);
   wire [0:0] abc9_ff.init = 1'b0;
   wire [0:0] _TECHMAP_REPLACE_.abc9_ff.Q = $QQ;
 endmodule
-module FDPE_1 (output Q, input C, CE, D, PRE);
+module FDPE_1 (output Q, (* techmap_autopurge *) input C, CE, D, PRE);
   parameter [0:0] INIT = 1'b1;
   wire QQ, $Q, $QQ;
   generate if (INIT == 1'b1) begin
diff --git a/techlibs/xilinx/abc9_model.v b/techlibs/xilinx/abc9_model.v
index 204fa883f..782c53ab6 100644
--- a/techlibs/xilinx/abc9_model.v
+++ b/techlibs/xilinx/abc9_model.v
@@ -33,6 +33,11 @@ endmodule
 module \$__ABC9_FF_ (input D, output Q);
 endmodule
 
+(* abc9_box_id = (9000+DELAY) *)
+module \$__ABC9_DELAY (input I, output O);
+  parameter DELAY = 0;
+endmodule
+
 // Box to emulate async behaviour of FDC*
 (* abc9_box_id = 1000, lib_whitebox *)
 module \$__ABC9_ASYNC0 (input A, S, output Y);
@@ -42,7 +47,7 @@ endmodule
 // Box to emulate async behaviour of FDP*
 (* abc9_box_id = 1001, lib_whitebox *)
 module \$__ABC9_ASYNC1 (input A, S, output Y);
-  assign Y = S ? 1'b0 : A;
+  assign Y = S ? 1'b1 : A;
 endmodule
 
 // Box to emulate comb/seq behaviour of RAM{32,64} and SRL{16,32}
diff --git a/techlibs/xilinx/abc9_xc7.box b/techlibs/xilinx/abc9_xc7.box
index 13f4f0e61..48d492801 100644
--- a/techlibs/xilinx/abc9_xc7.box
+++ b/techlibs/xilinx/abc9_xc7.box
@@ -62,67 +62,6 @@ $__ABC9_ASYNC1 1001 1   2   1
 #A S
 0  764 # Y
 
-# Flop boxes:
-# * Max delays from https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/slicel.sdf#L237-L251
-#                   https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/slicel.sdf#L265-L277
-# * Exception: $abc9_currQ is a special input (located last) necessary for clock-enable functionality
-
-# Box 1100 : FDRE
-# name ID   w/b ins outs
-FDRE   1100 1   5   1
-#C CE  D   R   $abc9_currQ
-#0 109 -46 404 0
-0 109 0   404 0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1101 : FDRE_1
-# name ID   w/b ins outs
-FDRE_1 1101 1   5   1
-#C CE  D   R   $abc9_currQ
-#0 109 -46 404 0
-0 109 0   404 0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1102 : FDSE
-# name ID   w/b ins outs
-FDSE   1102 1   5   1
-#C CE  D   R   $abc9_currQ
-#0 109 -46 404 0
-0 109 0   404 0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1103 : FDSE_1
-# name ID   w/b ins outs
-FDSE_1 1103 1   5   1
-#C CE  D   R   $abc9_currQ
-#0 109 -46 404 0
-0 109 0   404 0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1104 : FDCE
-# name ID   w/b ins outs
-FDCE   1104 1   5   1
-#C CE  CLR D   $abc9_currQ
-#0 109 764 -46 0
-0 109 764 0   0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1105 : FDCE_1
-# name ID   w/b ins outs
-FDCE_1 1105 1   5   1
-#C CE  CLR D   $abc9_currQ
-#0 109 764 -46 0
-0 109 764 0   0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1106 : FDPE
-# name ID   w/b ins outs
-FDPE   1106 1   5   1
-#C CE  D   PRE $abc9_currQ
-#0 109 -46 764 0
-0 109 0   764 0 # Q (-46ps Tsu clamped to 0)
-
-# Box 1107 : FDPE_1
-# name ID   w/b ins outs
-FDPE_1 1107 1   5   1
-#C CE  D   PRE $abc9_currQ
-#0 109 -46 764 0
-0 109 0   764 0 # Q (-46ps Tsu clamped to 0)
-
 # Box 2000 : $__ABC9_LUT6
 #            (private cell to emulate async behaviour of LUTRAMs)
 # SLICEM/A6LUT
diff --git a/techlibs/xilinx/cells_sim.v b/techlibs/xilinx/cells_sim.v
index 22dca3c47..4692eba33 100644
--- a/techlibs/xilinx/cells_sim.v
+++ b/techlibs/xilinx/cells_sim.v
@@ -325,17 +325,20 @@ endmodule
 
 // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLL_L.sdf#L238-L250
 
-(* abc9_box_id=1100, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDRE (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_C_INVERTED" *)
   input C,
+  (* abc9_required=109 *)
   input CE,
   (* invertible_pin = "IS_D_INVERTED" *)
+  //(* abc9_required=-46 *) // Negative required times not currently supported
   input D,
   (* invertible_pin = "IS_R_INVERTED" *)
+  (* abc9_required=404 *)
   input R
 );
   parameter [0:0] INIT = 1'b0;
@@ -349,30 +352,38 @@ module FDRE (
   endcase endgenerate
 endmodule
 
-(* abc9_box_id=1101, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDRE_1 (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   input C,
-  input CE, D, R
+  (* abc9_required=109 *)
+  input CE,
+  //(* abc9_required=-46 *) // Negative required times not currently supported
+  input D,
+  (* abc9_required=404 *)
+  input R
 );
   parameter [0:0] INIT = 1'b0;
   initial Q <= INIT;
   always @(negedge C) if (R) Q <= 1'b0; else if (CE) Q <= D;
 endmodule
 
-(* abc9_box_id=1102, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDSE (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_C_INVERTED" *)
   input C,
+  (* abc9_required=109 *)
   input CE,
   (* invertible_pin = "IS_D_INVERTED" *)
+  //(* abc9_required=-46 *) // Negative required times not currently supported
   input D,
   (* invertible_pin = "IS_S_INVERTED" *)
+  (* abc9_required=404 *)
   input S
 );
   parameter [0:0] INIT = 1'b1;
@@ -386,13 +397,18 @@ module FDSE (
   endcase endgenerate
 endmodule
 
-(* abc9_box_id=1103, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDSE_1 (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   input C,
-  input CE, D, S
+  (* abc9_required=109 *)
+  input CE,
+  //(* abc9_required=-46 *) // Negative required times not currently supported
+  input D,
+  (* abc9_required=404 *)
+  input S
 );
   parameter [0:0] INIT = 1'b1;
   initial Q <= INIT;
@@ -405,6 +421,7 @@ module FDRSE (
   (* invertible_pin = "IS_C_INVERTED" *)
   input C,
   (* invertible_pin = "IS_CE_INVERTED" *)
+  (* abc9_required=109 *)
   input CE,
   (* invertible_pin = "IS_D_INVERTED" *)
   input D,
@@ -434,17 +451,20 @@ module FDRSE (
       Q <= d;
 endmodule
 
-(* abc9_box_id=1104, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDCE (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_C_INVERTED" *)
   input C,
+  (* abc9_required=109 *)
   input CE,
   (* invertible_pin = "IS_CLR_INVERTED" *)
+  (* abc9_required=764 *)
   input CLR,
   (* invertible_pin = "IS_D_INVERTED" *)
+  //(* abc9_required=-46 *) // Negative required times not currently supported
   input D
 );
   parameter [0:0] INIT = 1'b0;
@@ -460,30 +480,38 @@ module FDCE (
   endcase endgenerate
 endmodule
 
-(* abc9_box_id=1105, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDCE_1 (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   input C,
-  input CE, D, CLR
+  (* abc9_required=109 *)
+  input CE,
+  (* abc9_required=764 *)
+  input CLR,
+  //(* abc9_required=-46 *) // Negative required times not currently supported
+  input D
 );
   parameter [0:0] INIT = 1'b0;
   initial Q <= INIT;
   always @(negedge C, posedge CLR) if (CLR) Q <= 1'b0; else if (CE) Q <= D;
 endmodule
 
-(* abc9_box_id=1106, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDPE (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_C_INVERTED" *)
   input C,
+  (* abc9_required=109 *)
   input CE,
   (* invertible_pin = "IS_D_INVERTED" *)
+  //(* abc9_required=-46 *) // Negative required times not currently supported
   input D,
   (* invertible_pin = "IS_PRE_INVERTED" *)
+  (* abc9_required=764 *)
   input PRE
 );
   parameter [0:0] INIT = 1'b1;
@@ -499,13 +527,18 @@ module FDPE (
   endcase endgenerate
 endmodule
 
-(* abc9_box_id=1107, lib_whitebox, abc9_flop *)
+(* abc9_flop, lib_whitebox *)
 module FDPE_1 (
   (* abc9_arrival=303 *)
   output reg Q,
   (* clkbuf_sink *)
   input C,
-  input CE, D, PRE
+  (* abc9_required=109 *)
+  input CE,
+  //(* abc9_required=-46 *) // Negative required times not currently supported
+  input D,
+  (* abc9_required=764 *)
+  input PRE
 );
   parameter [0:0] INIT = 1'b1;
   initial Q <= INIT;
@@ -1120,15 +1153,33 @@ module RAM16X1D_1 (
 endmodule
 
 module RAM32X1D (
-  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L857
-  (* abc9_arrival=1188 *)
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L981
+  (* abc9_arrival=1153 *)
   output DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986
+  (* abc9_required=453 *)
   input  D,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
   input  WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
   input  WE,
-  input  A0, A1, A2, A3, A4,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L800
+  (* abc9_required=245 *)
+  input  A0,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/clBLM_R.sdf#L798
+  (* abc9_required=208 *)
+  input  A1,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L796
+  (* abc9_required=147 *)
+  input  A2,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L794
+  (* abc9_required=68 *)
+  input  A3,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L792
+  (* abc9_required=66 *)
+  input  A4,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4
 );
   parameter INIT = 32'h0;
@@ -1143,15 +1194,33 @@ module RAM32X1D (
 endmodule
 
 module RAM32X1D_1 (
-  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L857
-  (* abc9_arrival=1188 *)
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L981
+  (* abc9_arrival=1153 *)
   output DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986
+  (* abc9_required=453 *)
   input  D,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
   input  WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
   input  WE,
-  input  A0, A1, A2, A3, A4,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L800
+  (* abc9_required=245 *)
+  input  A0,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/clBLM_R.sdf#L798
+  (* abc9_required=208 *)
+  input  A1,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L796
+  (* abc9_required=147 *)
+  input  A2,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L794
+  (* abc9_required=68 *)
+  input  A3,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L792
+  (* abc9_required=66 *)
+  input  A4,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4
 );
   parameter INIT = 32'h0;
@@ -1166,15 +1235,36 @@ module RAM32X1D_1 (
 endmodule
 
 module RAM64X1D (
-  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L889
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L981
   (* abc9_arrival=1153 *)
   output DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986
+  (* abc9_required=453 *)
   input  D,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
   input  WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
   input  WE,
-  input  A0, A1, A2, A3, A4, A5,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L828
+  (* abc9_required=362 *)
+  input  A0,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L826
+  (* abc9_required=245 *)
+  input  A1,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L824
+  (* abc9_required=208 *)
+  input  A2,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L822
+  (* abc9_required=147 *)
+  input  A3,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L820
+  (* abc9_required=68 *)
+  input  A4,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L818
+  (* abc9_required=66 *)
+  input  A5,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5
 );
   parameter INIT = 64'h0;
@@ -1189,15 +1279,36 @@ module RAM64X1D (
 endmodule
 
 module RAM64X1D_1 (
-  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L889
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L981
   (* abc9_arrival=1153 *)
   output DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986
+  (* abc9_required=453 *)
   input  D,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
   input  WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
   input  WE,
-  input  A0, A1, A2, A3, A4, A5,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L828
+  (* abc9_required=362 *)
+  input  A0,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L826
+  (* abc9_required=245 *)
+  input  A1,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L824
+  (* abc9_required=208 *)
+  input  A2,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L822
+  (* abc9_required=147 *)
+  input  A3,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L820
+  (* abc9_required=68 *)
+  input  A4,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L818
+  (* abc9_required=66 *)
+  input  A5,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5
 );
   parameter INIT = 64'h0;
@@ -1212,16 +1323,23 @@ module RAM64X1D_1 (
 endmodule
 
 module RAM128X1D (
-  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L889
-  //   plus 204ps to cross MUXF7
-  (* abc9_arrival=1357 *)
-  output DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L981
+  //   plus 208ps to cross MUXF7
+  (* abc9_arrival=1359 *)
+  output       DPO, SPO,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986
+  (* abc9_required=453 *)
   input        D,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
   input        WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
   input        WE,
-  input  [6:0] A, DPRA
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L818-830
+  (* abc9_required="616 362 245 208 147 68 66" *)
+  input  [6:0] A,
+  input  [6:0] DPRA
 );
   parameter INIT = 128'h0;
   parameter IS_WCLK_INVERTED = 1'b0;
@@ -1253,24 +1371,44 @@ endmodule
 // Multi port.
 
 module RAM32M (
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L889
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L857
-  (* abc9_arrival=1188 *)
+  (* abc9_arrival="1153 1188" *)
   output [1:0] DOA,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L957
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L925
-  (* abc9_arrival=1187 *)
+  (* abc9_arrival="1161 1187" *)
   output [1:0] DOB,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L1025
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L993
-  (* abc9_arrival=1180 *)
+  (* abc9_arrival="1158 1180" *)
   output [1:0] DOC,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L1093
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L1061
-  (* abc9_arrival=1190 *)
+  (* abc9_arrival="1163 1190" *)
   output [1:0] DOD,
-  input [4:0] ADDRA, ADDRB, ADDRC, ADDRD,
-  input [1:0] DIA, DIB, DIC, DID,
+  input  [4:0] ADDRA, ADDRB, ADDRC,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L792-L802
+  (* abc9_required="245 208 147 68 66" *)
+  input  [4:0] ADDRD,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986-L988
+  (* abc9_required="453 384" *)
+  input  [1:0] DIA,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1054-L1056
+  (* abc9_required="461 354" *)
+  input  [1:0] DIB,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1122-L1124
+  (* abc9_required="457 375" *)
+  input  [1:0] DIC,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1190-L1192
+  (* abc9_required="310 334" *)
+  input  [1:0] DID,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
-  input WCLK,
-  input WE
+  input        WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
+  input        WE
 );
   parameter [63:0] INIT_A = 64'h0000000000000000;
   parameter [63:0] INIT_B = 64'h0000000000000000;
@@ -1367,22 +1505,38 @@ endmodule
 module RAM64M (
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L889
   (* abc9_arrival=1153 *)
-  output DOA,
+  output       DOA,
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L957
   (* abc9_arrival=1161 *)
-  output DOB,
+  output       DOB,
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L1025
   (* abc9_arrival=1158 *)
-  output DOC,
+  output       DOC,
   // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L1093
   (* abc9_arrival=1163 *)
-  output DOD,
-  input [5:0] ADDRA, ADDRB, ADDRC, ADDRD,
-  input DIA, DIB, DIC, DID,
+  output       DOD,
+  input  [5:0] ADDRA, ADDRB, ADDRC,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L818-L830
+  (* abc9_required="362 245 208 147 68 66" *)
+  input  [5:0] ADDRD,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L986-L988
+  (* abc9_required=384 *)
+  input        DIA,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1054-L1056
+  (* abc9_required=354 *)
+  input        DIB,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1122-L1124
+  (* abc9_required=375 *)
+  input        DIC,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L1190-L1192
+  (* abc9_required=310 *)
+  input        DID,
   (* clkbuf_sink *)
   (* invertible_pin = "IS_WCLK_INVERTED" *)
-  input WCLK,
-  input WE
+  input        WCLK,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/31f51ac5ec7448dd6f79a8267f147123e4413c21/artix7/timings/CLBLM_R.sdf#L834
+  (* abc9_required=654 *)
+  input        WE
 );
   parameter [63:0] INIT_A = 64'h0000000000000000;
   parameter [63:0] INIT_B = 64'h0000000000000000;
@@ -2155,7 +2309,235 @@ assign PCOUT = P;
 
 endmodule
 
-// TODO: DSP48 (Virtex 4).
+module DSP48 (
+    input signed [17:0] A,
+    input signed [17:0] B,
+    input signed [47:0] C,
+    input signed [17:0] BCIN,
+    input signed [47:0] PCIN,
+    input CARRYIN,
+    input [6:0] OPMODE,
+    input SUBTRACT,
+    input [1:0] CARRYINSEL,
+    output signed [47:0] P,
+    output signed [17:0] BCOUT,
+    output signed [47:0] PCOUT,
+    (* clkbuf_sink *)
+    input CLK,
+    input CEA,
+    input CEB,
+    input CEC,
+    input CEM,
+    input CECARRYIN,
+    input CECINSUB,
+    input CECTRL,
+    input CEP,
+    input RSTA,
+    input RSTB,
+    input RSTC,
+    input RSTM,
+    input RSTCARRYIN,
+    input RSTCTRL,
+    input RSTP
+);
+
+parameter integer AREG = 1;
+parameter integer BREG = 1;
+parameter integer CREG = 1;
+parameter integer MREG = 1;
+parameter integer PREG = 1;
+parameter integer CARRYINREG = 1;
+parameter integer CARRYINSELREG = 1;
+parameter integer OPMODEREG = 1;
+parameter integer SUBTRACTREG = 1;
+parameter B_INPUT = "DIRECT";
+parameter LEGACY_MODE = "MULT18X18S";
+
+wire signed [17:0] A_OUT;
+wire signed [17:0] B_OUT;
+wire signed [47:0] C_OUT;
+wire signed [35:0] M_MULT;
+wire signed [35:0] M_OUT;
+wire signed [47:0] P_IN;
+wire [6:0] OPMODE_OUT;
+wire [1:0] CARRYINSEL_OUT;
+wire CARRYIN_OUT;
+wire SUBTRACT_OUT;
+reg INT_CARRYIN_XY;
+reg INT_CARRYIN_Z;
+reg signed [47:0] XMUX;
+reg signed [47:0] YMUX;
+wire signed [47:0] XYMUX;
+reg signed [47:0] ZMUX;
+reg CIN;
+
+// The B input multiplexer.
+wire signed [17:0] B_MUX;
+assign B_MUX = (B_INPUT == "DIRECT") ? B : BCIN;
+
+// The cascade output.
+assign BCOUT = B_OUT;
+assign PCOUT = P;
+
+// The registers.
+reg signed [17:0] A0_REG;
+reg signed [17:0] A1_REG;
+reg signed [17:0] B0_REG;
+reg signed [17:0] B1_REG;
+reg signed [47:0] C_REG;
+reg signed [35:0] M_REG;
+reg signed [47:0] P_REG;
+reg [6:0] OPMODE_REG;
+reg [1:0] CARRYINSEL_REG;
+reg SUBTRACT_REG;
+reg CARRYIN_REG;
+reg INT_CARRYIN_XY_REG;
+
+initial begin
+	A0_REG = 0;
+	A1_REG = 0;
+	B0_REG = 0;
+	B1_REG = 0;
+	C_REG = 0;
+	M_REG = 0;
+	P_REG = 0;
+	OPMODE_REG = 0;
+	CARRYINSEL_REG = 0;
+	SUBTRACT_REG = 0;
+	CARRYIN_REG = 0;
+	INT_CARRYIN_XY_REG = 0;
+end
+
+always @(posedge CLK) begin
+	if (RSTA) begin
+		A0_REG <= 0;
+		A1_REG <= 0;
+	end else if (CEA) begin
+		A0_REG <= A;
+		A1_REG <= A0_REG;
+	end
+	if (RSTB) begin
+		B0_REG <= 0;
+		B1_REG <= 0;
+	end else if (CEB) begin
+		B0_REG <= B_MUX;
+		B1_REG <= B0_REG;
+	end
+	if (RSTC) begin
+		C_REG <= 0;
+	end else if (CEC) begin
+		C_REG <= C;
+	end
+	if (RSTM) begin
+		M_REG <= 0;
+	end else if (CEM) begin
+		M_REG <= M_MULT;
+	end
+	if (RSTP) begin
+		P_REG <= 0;
+	end else if (CEP) begin
+		P_REG <= P_IN;
+	end
+	if (RSTCTRL) begin
+		OPMODE_REG <= 0;
+		CARRYINSEL_REG <= 0;
+		SUBTRACT_REG <= 0;
+	end else begin
+		if (CECTRL) begin
+			OPMODE_REG <= OPMODE;
+			CARRYINSEL_REG <= CARRYINSEL;
+		end
+		if (CECINSUB)
+			SUBTRACT_REG <= SUBTRACT;
+	end
+	if (RSTCARRYIN) begin
+		CARRYIN_REG <= 0;
+		INT_CARRYIN_XY_REG <= 0;
+	end else begin
+		if (CECINSUB)
+			CARRYIN_REG <= CARRYIN;
+		if (CECARRYIN)
+			INT_CARRYIN_XY_REG <= INT_CARRYIN_XY;
+	end
+end
+
+// The register enables.
+assign A_OUT = (AREG == 2) ? A1_REG : (AREG == 1) ? A0_REG : A;
+assign B_OUT = (BREG == 2) ? B1_REG : (BREG == 1) ? B0_REG : B_MUX;
+assign C_OUT = (CREG == 1) ? C_REG : C;
+assign M_OUT = (MREG == 1) ? M_REG : M_MULT;
+assign P = (PREG == 1) ? P_REG : P_IN;
+assign OPMODE_OUT = (OPMODEREG == 1) ? OPMODE_REG : OPMODE;
+assign SUBTRACT_OUT = (SUBTRACTREG == 1) ? SUBTRACT_REG : SUBTRACT;
+assign CARRYINSEL_OUT = (CARRYINSELREG == 1) ? CARRYINSEL_REG : CARRYINSEL;
+assign CARRYIN_OUT = (CARRYINREG == 1) ? CARRYIN_REG : CARRYIN;
+
+// The multiplier.
+assign M_MULT = A_OUT * B_OUT;
+
+// The post-adder inputs.
+always @* begin
+	case (OPMODE_OUT[1:0])
+		2'b00: XMUX <= 0;
+		2'b10: XMUX <= P;
+		2'b11: XMUX <= {{12{A_OUT[17]}}, A_OUT, B_OUT};
+		default: XMUX <= 48'hxxxxxxxxxxxx;
+	endcase
+	case (OPMODE_OUT[1:0])
+		2'b01: INT_CARRYIN_XY <= A_OUT[17] ~^ B_OUT[17];
+		2'b11: INT_CARRYIN_XY <= ~A_OUT[17];
+		// TODO: not tested in hardware.
+		default: INT_CARRYIN_XY <= A_OUT[17] ~^ B_OUT[17];
+	endcase
+end
+
+always @* begin
+	case (OPMODE_OUT[3:2])
+		2'b00: YMUX <= 0;
+		2'b11: YMUX <= C_OUT;
+		default: YMUX <= 48'hxxxxxxxxxxxx;
+	endcase
+end
+
+assign XYMUX = (OPMODE_OUT[3:0] == 4'b0101) ? M_OUT : (XMUX + YMUX);
+
+always @* begin
+	case (OPMODE_OUT[6:4])
+		3'b000: ZMUX <= 0;
+		3'b001: ZMUX <= PCIN;
+		3'b010: ZMUX <= P;
+		3'b011: ZMUX <= C_OUT;
+		3'b101: ZMUX <= {{17{PCIN[47]}}, PCIN[47:17]};
+		3'b110: ZMUX <= {{17{P[47]}}, P[47:17]};
+		default: ZMUX <= 48'hxxxxxxxxxxxx;
+	endcase
+	// TODO: check how all this works on actual hw.
+	if (OPMODE_OUT[1:0] == 2'b10)
+		INT_CARRYIN_Z <= ~P[47];
+	else
+		case (OPMODE_OUT[6:4])
+			3'b001: INT_CARRYIN_Z <= ~PCIN[47];
+			3'b010: INT_CARRYIN_Z <= ~P[47];
+			3'b101: INT_CARRYIN_Z <= ~PCIN[47];
+			3'b110: INT_CARRYIN_Z <= ~P[47];
+			default: INT_CARRYIN_Z <= 1'bx;
+		endcase
+end
+
+always @* begin
+	case (CARRYINSEL_OUT)
+		2'b00: CIN <= CARRYIN_OUT;
+		2'b01: CIN <= INT_CARRYIN_Z;
+		2'b10: CIN <= INT_CARRYIN_XY;
+		2'b11: CIN <= INT_CARRYIN_XY_REG;
+		default: CIN <= 1'bx;
+	endcase
+end
+
+// The post-adder.
+assign P_IN = SUBTRACT_OUT ? (ZMUX - (XYMUX + CIN)) : (ZMUX + XYMUX + CIN);
+
+endmodule
 
 // TODO: DSP48E (Virtex 5).
 
@@ -2169,21 +2551,30 @@ module DSP48E1 (
     output reg MULTSIGNOUT,
     output OVERFLOW,
 `ifdef YOSYS
-    (* abc9_arrival = \DSP48E1.P_arrival () *)
+    (* abc9_arrival = \P.abc9_arrival () *)
 `endif
     output reg signed [47:0] P,
     output reg PATTERNBDETECT,
     output reg PATTERNDETECT,
 `ifdef YOSYS
-    (* abc9_arrival = \DSP48E1.PCOUT_arrival () *)
+    (* abc9_arrival = \PCOUT.abc9_arrival () *)
 `endif
     output [47:0] PCOUT,
     output UNDERFLOW,
+`ifdef YOSYS
+    (* abc9_required = \A.abc9_required () *)
+`endif
     input signed [29:0] A,
     input [29:0] ACIN,
     input [3:0] ALUMODE,
+`ifdef YOSYS
+    (* abc9_required = \B.abc9_required () *)
+`endif
     input signed [17:0] B,
     input [17:0] BCIN,
+`ifdef YOSYS
+    (* abc9_required = \C.abc9_required () *)
+`endif
     input [47:0] C,
     input CARRYCASCIN,
     input CARRYIN,
@@ -2202,10 +2593,16 @@ module DSP48E1 (
     input CEM,
     input CEP,
     (* clkbuf_sink *) input CLK,
+`ifdef YOSYS
+    (* abc9_required = \D.abc9_required () *)
+`endif
     input [24:0] D,
     input [4:0] INMODE,
     input MULTSIGNIN,
     input [6:0] OPMODE,
+`ifdef YOSYS
+    (* abc9_required = \PCIN.abc9_required () *)
+`endif
     input [47:0] PCIN,
     input RSTA,
     input RSTALLCARRYIN,
@@ -2250,69 +2647,133 @@ module DSP48E1 (
     parameter [6:0] IS_OPMODE_INVERTED = 7'b0;
 
 `ifdef YOSYS
-    function integer \DSP48E1.P_arrival ;
+    function integer \A.abc9_required ;
     begin
-        \DSP48E1.P_arrival = 0;
-        if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
-            if (PREG != 0)      \DSP48E1.P_arrival =  329;
-            // Worst-case from CREG and MREG
-            else if (CREG != 0) \DSP48E1.P_arrival = 1687;
-            else if (MREG != 0) \DSP48E1.P_arrival = 1671;
-            // Worst-case from AREG and BREG
-            else if (AREG != 0) \DSP48E1.P_arrival = 2952;
-            else if (BREG != 0) \DSP48E1.P_arrival = 2813;
+        \A.abc9_required = 0;
+        if (AREG != 0)           \A.abc9_required =  254;
+        else if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
+            if (MREG != 0)       \A.abc9_required = 1416;
+            else if (PREG != 0)  \A.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 3030 : 2739) ;
         end
         else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin
-            if (PREG != 0)      \DSP48E1.P_arrival =  329;
-            // Worst-case from CREG and MREG
-            else if (CREG != 0) \DSP48E1.P_arrival = 1687;
-            else if (MREG != 0) \DSP48E1.P_arrival = 1671;
-            // Worst-case from AREG, ADREG, BREG, DREG
-            else if (AREG != 0)  \DSP48E1.P_arrival = 3935;
-            else if (DREG != 0)  \DSP48E1.P_arrival = 3908;
-            else if (ADREG != 0) \DSP48E1.P_arrival = 2958;
-            else if (BREG != 0)  \DSP48E1.P_arrival = 2813;
+            // Worst-case from ADREG and MREG
+            if (MREG != 0)       \A.abc9_required = 2400;
+            else if (ADREG != 0) \A.abc9_required = 1283;
+            else if (PREG != 0)  \A.abc9_required = 3723;
+            else if (PREG != 0)  \A.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 4014 : 3723) ;
         end
         else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin
-            if (PREG != 0)      \DSP48E1.P_arrival =  329;
+            if (PREG != 0)       \A.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 1730 : 1441) ;
+        end
+    end
+    endfunction
+    function integer \B.abc9_required ;
+    begin
+        \B.abc9_required = 0;
+        if (BREG != 0)      \B.abc9_required =  324;
+        else if (MREG != 0) \B.abc9_required = 1285;
+        else if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
+            if (PREG != 0)  \B.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 2898 : 2608) ;
+        end
+        else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin
+            if (PREG != 0)  \B.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 2898 : 2608) ;
+        end
+        else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin
+            if (PREG != 0)  \B.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 1718 : 1428) ;
+        end
+    end
+    endfunction
+    function integer \C.abc9_required ;
+    begin
+        \C.abc9_required = 0;
+        if (CREG != 0)      \C.abc9_required =  168;
+        else if (PREG != 0) \C.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 1534 : 1244) ;
+    end
+    endfunction
+    function integer \D.abc9_required ;
+    begin
+        \D.abc9_required = 0;
+        if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
+        end
+        else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin
+            if (DREG != 0)       \D.abc9_required =  248;
+            else if (ADREG != 0) \D.abc9_required = 1195;
+            else if (MREG != 0)  \D.abc9_required = 2310;
+            else if (PREG != 0)  \D.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 3925 : 3635) ;
+        end
+        else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin
+        end
+    end
+    endfunction
+    function integer \PCIN.abc9_required ;
+    begin
+        \PCIN.abc9_required = 0;
+        if (PREG != 0) \PCIN.abc9_required = (USE_PATTERN_DETECT != "NO_PATDET" ? 1315 : 1025) ;
+    end
+    endfunction
+    function integer \P.abc9_arrival ;
+    begin
+        \P.abc9_arrival = 0;
+        if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
+            if (PREG != 0)       \P.abc9_arrival =  329;
+            // Worst-case from CREG and MREG
+            else if (CREG != 0)  \P.abc9_arrival = 1687;
+            else if (MREG != 0)  \P.abc9_arrival = 1671;
+            // Worst-case from AREG and BREG
+            else if (AREG != 0)  \P.abc9_arrival = 2952;
+            else if (BREG != 0)  \P.abc9_arrival = 2813;
+        end
+        else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin
+            if (PREG != 0)       \P.abc9_arrival =  329;
+            // Worst-case from CREG and MREG
+            else if (CREG != 0)  \P.abc9_arrival = 1687;
+            else if (MREG != 0)  \P.abc9_arrival = 1671;
+            // Worst-case from AREG, ADREG, BREG, DREG
+            else if (AREG != 0)  \P.abc9_arrival = 3935;
+            else if (DREG != 0)  \P.abc9_arrival = 3908;
+            else if (ADREG != 0) \P.abc9_arrival = 2958;
+            else if (BREG != 0)  \P.abc9_arrival = 2813;
+        end
+        else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin
+            if (PREG != 0)       \P.abc9_arrival =  329;
             // Worst-case from AREG, BREG, CREG
-            else if (CREG != 0) \DSP48E1.P_arrival = 1687;
-            else if (AREG != 0) \DSP48E1.P_arrival = 1632;
-            else if (BREG != 0) \DSP48E1.P_arrival = 1616;
+            else if (CREG != 0)  \P.abc9_arrival = 1687;
+            else if (AREG != 0)  \P.abc9_arrival = 1632;
+            else if (BREG != 0)  \P.abc9_arrival = 1616;
         end
         //else
         //    $error("Invalid DSP48E1 configuration");
     end
     endfunction
-    function integer \DSP48E1.PCOUT_arrival ;
+    function integer \PCOUT.abc9_arrival ;
     begin
-        \DSP48E1.PCOUT_arrival = 0;
+        \PCOUT.abc9_arrival = 0;
         if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin
-            if (PREG != 0)      \DSP48E1.PCOUT_arrival =  435;
+            if (PREG != 0)       \PCOUT.abc9_arrival =  435;
             // Worst-case from CREG and MREG
-            else if (CREG != 0) \DSP48E1.PCOUT_arrival = 1835;
-            else if (MREG != 0) \DSP48E1.PCOUT_arrival = 1819;
+            else if (CREG != 0)  \PCOUT.abc9_arrival = 1835;
+            else if (MREG != 0)  \PCOUT.abc9_arrival = 1819;
             // Worst-case from AREG and BREG
-            else if (AREG != 0) \DSP48E1.PCOUT_arrival = 3098;
-            else if (BREG != 0) \DSP48E1.PCOUT_arrival = 2960;
+            else if (AREG != 0)  \PCOUT.abc9_arrival = 3098;
+            else if (BREG != 0)  \PCOUT.abc9_arrival = 2960;
         end
         else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin
-            if (PREG != 0)      \DSP48E1.PCOUT_arrival =  435;
+            if (PREG != 0)       \PCOUT.abc9_arrival =  435;
             // Worst-case from CREG and MREG
-            else if (CREG != 0) \DSP48E1.PCOUT_arrival = 1835;
-            else if (MREG != 0) \DSP48E1.PCOUT_arrival = 1819;
+            else if (CREG != 0)  \PCOUT.abc9_arrival = 1835;
+            else if (MREG != 0)  \PCOUT.abc9_arrival = 1819;
             // Worst-case from AREG, ADREG, BREG, DREG
-            else if (AREG != 0)  \DSP48E1.PCOUT_arrival = 4083;
-            else if (DREG != 0)  \DSP48E1.PCOUT_arrival = 4056;
-            else if (BREG != 0)  \DSP48E1.PCOUT_arrival = 2960;
-            else if (ADREG != 0) \DSP48E1.PCOUT_arrival = 2859;
+            else if (AREG != 0)  \PCOUT.abc9_arrival = 4083;
+            else if (DREG != 0)  \PCOUT.abc9_arrival = 4056;
+            else if (BREG != 0)  \PCOUT.abc9_arrival = 2960;
+            else if (ADREG != 0) \PCOUT.abc9_arrival = 2859;
         end
         else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin
-            if (PREG != 0)      \DSP48E1.PCOUT_arrival =  435;
+            if (PREG != 0)       \PCOUT.abc9_arrival =  435;
             // Worst-case from AREG, BREG, CREG
-            else if (CREG != 0) \DSP48E1.PCOUT_arrival = 1835;
-            else if (AREG != 0) \DSP48E1.PCOUT_arrival = 1780;
-            else if (BREG != 0) \DSP48E1.PCOUT_arrival = 1765;
+            else if (CREG != 0)  \PCOUT.abc9_arrival = 1835;
+            else if (AREG != 0)  \PCOUT.abc9_arrival = 1780;
+            else if (BREG != 0)  \PCOUT.abc9_arrival = 1765;
         end
         //else
         //    $error("Invalid DSP48E1 configuration");
diff --git a/techlibs/xilinx/cells_xtra.py b/techlibs/xilinx/cells_xtra.py
index d5c58c5d7..75646f594 100644
--- a/techlibs/xilinx/cells_xtra.py
+++ b/techlibs/xilinx/cells_xtra.py
@@ -180,18 +180,58 @@ CELLS = [
     Cell('RAMB18E1', port_attrs={
         'CLKARDCLK': ['clkbuf_sink'],
         'CLKBWRCLK': ['clkbuf_sink'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L143
         'DOADO': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L163
         'DOBDO': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L144
         'DOPADOP': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L164
         'DOPBDOP': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L13
+        'ADDRARDADDR': ['abc9_required=566'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L17
+        'ADDRBWRADDR': ['abc9_required=566'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L19
+        'WEA': ['abc9_required=532'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L21
+        'WEBWE': ['abc9_required=532'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L123
+        'DIADI': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L133
+        'DIBDI': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L125
+        'DIPADIP': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L135
+        'DIPBDIP': ['abc9_required=737'],
     }),
     Cell('RAMB36E1', port_attrs={
         'CLKARDCLK': ['clkbuf_sink'],
         'CLKBWRCLK': ['clkbuf_sink'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L143
         'DOADO': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L163
         'DOBDO': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L144
         'DOPADOP': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L164
         'DOPBDOP': ['abc9_arrival=2454'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L13
+        'ADDRARDADDR': ['abc9_required=566'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L17
+        'ADDRBWRADDR': ['abc9_required=566'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L19
+        'WEA': ['abc9_required=532'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L21
+        'WEBWE': ['abc9_required=532'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L123
+        'DIADI': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L133
+        'DIBDI': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L125
+        'DIPADIP': ['abc9_required=737'],
+        # https://github.com/SymbiFlow/prjxray-db/blob/23c8b0851f979f0799318eaca90174413a46b257/artix7/timings/BRAM_L.sdf#L135
+        'DIPBDIP': ['abc9_required=737'],
     }),
     # Ultrascale.
     Cell('FIFO18E2', port_attrs={'RDCLK': ['clkbuf_sink'], 'WRCLK': ['clkbuf_sink']}),
@@ -209,7 +249,7 @@ CELLS = [
     # Cell('MULT18X18SIO', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3E
     # Cell('DSP48A', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3A DSP
     # Cell('DSP48A1', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 6
-    Cell('DSP48', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 4
+    # Cell('DSP48', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 4
     Cell('DSP48E', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 5
     #Cell('DSP48E1', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 6 / Series 7
     Cell('DSP48E2', port_attrs={'CLK': ['clkbuf_sink']}), # Ultrascale
diff --git a/techlibs/xilinx/cells_xtra.v b/techlibs/xilinx/cells_xtra.v
index c3e5c72f9..e87f4ec76 100644
--- a/techlibs/xilinx/cells_xtra.v
+++ b/techlibs/xilinx/cells_xtra.v
@@ -4518,13 +4518,21 @@ module RAMB18E1 (...);
     input RSTREGARSTREG;
     (* invertible_pin = "IS_RSTREGB_INVERTED" *)
     input RSTREGB;
+    (* abc9_required=566 *)
     input [13:0] ADDRARDADDR;
+    (* abc9_required=566 *)
     input [13:0] ADDRBWRADDR;
+    (* abc9_required=737 *)
     input [15:0] DIADI;
+    (* abc9_required=737 *)
     input [15:0] DIBDI;
+    (* abc9_required=737 *)
     input [1:0] DIPADIP;
+    (* abc9_required=737 *)
     input [1:0] DIPBDIP;
+    (* abc9_required=532 *)
     input [1:0] WEA;
+    (* abc9_required=532 *)
     input [3:0] WEBWE;
 endmodule
 
@@ -4742,13 +4750,21 @@ module RAMB36E1 (...);
     input REGCEB;
     input INJECTDBITERR;
     input INJECTSBITERR;
+    (* abc9_required=566 *)
     input [15:0] ADDRARDADDR;
+    (* abc9_required=566 *)
     input [15:0] ADDRBWRADDR;
+    (* abc9_required=737 *)
     input [31:0] DIADI;
+    (* abc9_required=737 *)
     input [31:0] DIBDI;
+    (* abc9_required=737 *)
     input [3:0] DIPADIP;
+    (* abc9_required=737 *)
     input [3:0] DIPBDIP;
+    (* abc9_required=532 *)
     input [3:0] WEA;
+    (* abc9_required=532 *)
     input [7:0] WEBWE;
 endmodule
 
@@ -5476,49 +5492,6 @@ module URAM288_BASE (...);
     input SLEEP;
 endmodule
 
-module DSP48 (...);
-    parameter integer AREG = 1;
-    parameter integer BREG = 1;
-    parameter B_INPUT = "DIRECT";
-    parameter integer CARRYINREG = 1;
-    parameter integer CARRYINSELREG = 1;
-    parameter integer CREG = 1;
-    parameter LEGACY_MODE = "MULT18X18S";
-    parameter integer MREG = 1;
-    parameter integer OPMODEREG = 1;
-    parameter integer PREG = 1;
-    parameter integer SUBTRACTREG = 1;
-    output [17:0] BCOUT;
-    output [47:0] P;
-    output [47:0] PCOUT;
-    input [17:0] A;
-    input [17:0] B;
-    input [17:0] BCIN;
-    input [47:0] C;
-    input CARRYIN;
-    input [1:0] CARRYINSEL;
-    input CEA;
-    input CEB;
-    input CEC;
-    input CECARRYIN;
-    input CECINSUB;
-    input CECTRL;
-    input CEM;
-    input CEP;
-    (* clkbuf_sink *)
-    input CLK;
-    input [6:0] OPMODE;
-    input [47:0] PCIN;
-    input RSTA;
-    input RSTB;
-    input RSTC;
-    input RSTCARRYIN;
-    input RSTCTRL;
-    input RSTM;
-    input RSTP;
-    input SUBTRACT;
-endmodule
-
 module DSP48E (...);
     parameter SIM_MODE = "SAFE";
     parameter integer ACASCREG = 1;
diff --git a/techlibs/xilinx/lut_map.v b/techlibs/xilinx/lut_map.v
index 62d501632..718ec42f1 100644
--- a/techlibs/xilinx/lut_map.v
+++ b/techlibs/xilinx/lut_map.v
@@ -29,90 +29,65 @@ module \$lut (A, Y);
   input [WIDTH-1:0] A;
   output Y;
 
-  // Need to swap input ordering, and fix init accordingly,
-  // to match ABC's expectation of LUT inputs in non-decreasing
-  // delay order
-  function [WIDTH-1:0] permute_index;
-      input [WIDTH-1:0] i;
-      integer j;
-      begin
-          permute_index = 0;
-          for (j = 0; j < WIDTH; j = j + 1)
-              permute_index[WIDTH-1 - j] = i[j];
-      end
-  endfunction
-
-  function [2**WIDTH-1:0] permute_init;
-      input [2**WIDTH-1:0] orig;
-      integer i;
-      begin
-          permute_init = 0;
-          for (i = 0; i < 2**WIDTH; i = i + 1)
-              permute_init[i] = orig[permute_index(i)];
-      end
-  endfunction
-
-  parameter [2**WIDTH-1:0] P_LUT = permute_init(LUT);
-
   generate
     if (WIDTH == 1) begin
-      if (P_LUT == 2'b01) begin
+      if (LUT == 2'b01) begin
         INV _TECHMAP_REPLACE_ (.O(Y), .I(A[0]));
       end else begin
-        LUT1 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        LUT1 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
           .I0(A[0]));
       end
     end else
     if (WIDTH == 2) begin
-      LUT2 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[1]), .I1(A[0]));
+      LUT2 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]));
     end else
     if (WIDTH == 3) begin
-      LUT3 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[2]), .I1(A[1]), .I2(A[0]));
+      LUT3 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]));
     end else
     if (WIDTH == 4) begin
-      LUT4 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[3]), .I1(A[2]), .I2(A[1]),
-        .I3(A[0]));
+      LUT4 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]));
     end else
     if (WIDTH == 5) begin
-      LUT5 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[4]), .I1(A[3]), .I2(A[2]),
-        .I3(A[1]), .I4(A[0]));
+      LUT5 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]));
     end else
     if (WIDTH == 6) begin
-      LUT6 #(.INIT(P_LUT)) _TECHMAP_REPLACE_ (.O(Y),
-        .I0(A[5]), .I1(A[4]), .I2(A[3]),
-        .I3(A[2]), .I4(A[1]), .I5(A[0]));
+      LUT6 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
     end else
     if (WIDTH == 7) begin
       wire T0, T1;
-      LUT6 #(.INIT(P_LUT[63:0])) fpga_lut_0 (.O(T0),
-        .I0(A[6]), .I1(A[5]), .I2(A[4]),
-        .I3(A[3]), .I4(A[2]), .I5(A[1]));
-      LUT6 #(.INIT(P_LUT[127:64])) fpga_lut_1 (.O(T1),
-        .I0(A[6]), .I1(A[5]), .I2(A[4]),
-        .I3(A[3]), .I4(A[2]), .I5(A[1]));
-      MUXF7 fpga_mux_0 (.O(Y), .I0(T0), .I1(T1), .S(A[0]));
+      LUT6 #(.INIT(LUT[63:0])) fpga_lut_0 (.O(T0),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      LUT6 #(.INIT(LUT[127:64])) fpga_lut_1 (.O(T1),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      MUXF7 fpga_mux_0 (.O(Y), .I0(T0), .I1(T1), .S(A[6]));
     end else
     if (WIDTH == 8) begin
       wire T0, T1, T2, T3, T4, T5;
-      LUT6 #(.INIT(P_LUT[63:0])) fpga_lut_0 (.O(T0),
-        .I0(A[7]), .I1(A[6]), .I2(A[5]),
-        .I3(A[4]), .I4(A[3]), .I5(A[2]));
-      LUT6 #(.INIT(P_LUT[127:64])) fpga_lut_1 (.O(T1),
-        .I0(A[7]), .I1(A[6]), .I2(A[5]),
-        .I3(A[4]), .I4(A[3]), .I5(A[2]));
-      LUT6 #(.INIT(P_LUT[191:128])) fpga_lut_2 (.O(T2),
-        .I0(A[7]), .I1(A[6]), .I2(A[5]),
-        .I3(A[4]), .I4(A[3]), .I5(A[2]));
-      LUT6 #(.INIT(P_LUT[255:192])) fpga_lut_3 (.O(T3),
-        .I0(A[7]), .I1(A[6]), .I2(A[5]),
-        .I3(A[4]), .I4(A[3]), .I5(A[2]));
-      MUXF7 fpga_mux_0 (.O(T4), .I0(T0), .I1(T1), .S(A[1]));
-      MUXF7 fpga_mux_1 (.O(T5), .I0(T2), .I1(T3), .S(A[1]));
-      MUXF8 fpga_mux_2 (.O(Y), .I0(T4), .I1(T5), .S(A[0]));
+      LUT6 #(.INIT(LUT[63:0])) fpga_lut_0 (.O(T0),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      LUT6 #(.INIT(LUT[127:64])) fpga_lut_1 (.O(T1),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      LUT6 #(.INIT(LUT[191:128])) fpga_lut_2 (.O(T2),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      LUT6 #(.INIT(LUT[255:192])) fpga_lut_3 (.O(T3),
+        .I0(A[0]), .I1(A[1]), .I2(A[2]),
+        .I3(A[3]), .I4(A[4]), .I5(A[5]));
+      MUXF7 fpga_mux_0 (.O(T4), .I0(T0), .I1(T1), .S(A[6]));
+      MUXF7 fpga_mux_1 (.O(T5), .I0(T2), .I1(T3), .S(A[6]));
+      MUXF8 fpga_mux_2 (.O(Y), .I0(T4), .I1(T5), .S(A[7]));
     end else begin
       wire _TECHMAP_FAIL_ = 1;
     end
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/techlibs/xilinx/synth_xilinx.cc b/techlibs/xilinx/synth_xilinx.cc
index 5c3b5179d..5a28bb139 100644
--- a/techlibs/xilinx/synth_xilinx.cc
+++ b/techlibs/xilinx/synth_xilinx.cc
@@ -316,7 +316,11 @@ struct SynthXilinxPass : public ScriptPass
 			run("proc");
 			if (flatten || help_mode)
 				run("flatten", "(with '-flatten')");
+			if (active_design)
+				active_design->scratchpad_unset("tribuf.added_something");
 			run("tribuf -logic");
+			if (noiopad && active_design && active_design->scratchpad_get_bool("tribuf.added_something"))
+				log_error("Tristate buffers are unsupported without the '-iopad' option.\n");
 			run("deminout");
 			run("opt_expr");
 			run("opt_clean");
@@ -526,7 +530,7 @@ struct SynthXilinxPass : public ScriptPass
 		if (check_label("map_cells")) {
 			// Needs to be done before logic optimization, so that inverters (OE vs T) are handled.
 			if (help_mode || !noiopad)
-				run("iopadmap -bits -outpad OBUF I:O -inpad IBUF O:I -toutpad $__XILINX_TOUTPAD OE:I:O -tinoutpad $__XILINX_TINOUTPAD OE:O:I:IO A:top", "(only if not '-noiopad')");
+				run("iopadmap -bits -outpad OBUF I:O -inpad IBUF O:I -toutpad $__XILINX_TOUTPAD OE:I:O -tinoutpad $__XILINX_TINOUTPAD OE:O:I:IO A:top", "(skip if '-noiopad')");
 			std::string techmap_args = "-map +/techmap.v -map +/xilinx/cells_map.v";
 			if (widemux > 0)
 				techmap_args += stringf(" -D MIN_MUX_INPUTS=%d", widemux);
@@ -589,17 +593,16 @@ struct SynthXilinxPass : public ScriptPass
 			if (!nosrl || help_mode)
 				run("xilinx_srl -fixed -minlen 3", "(skip if '-nosrl')");
 			std::string techmap_args = "-map +/xilinx/lut_map.v -map +/xilinx/cells_map.v";
-			if (help_mode)
-				techmap_args += stringf("[-map %s]", ff_map_file.c_str());
-			else if (!abc9)
+			if (help_mode || !abc9)
 				techmap_args += stringf(" -map %s", ff_map_file.c_str());
-			run("techmap " + techmap_args, "(only if '-abc9')");
+			run("techmap " + techmap_args);
 			run("xilinx_dffopt");
+			run("opt_lut_ins -tech xilinx");
 		}
 
 		if (check_label("finalize")) {
 			if (help_mode || !noclkbuf)
-				run("clkbufmap -buf BUFG O:I ", "(skip if '-noclkbuf')");
+				run("clkbufmap -buf BUFG O:I", "(skip if '-noclkbuf')");
 			if (help_mode || ise)
 				run("extractinv -inv INV O:I", "(only if '-ise')");
 			run("clean");
diff --git a/techlibs/xilinx/tests/.gitignore b/techlibs/xilinx/tests/.gitignore
index 848f88d53..0d9c28fde 100644
--- a/techlibs/xilinx/tests/.gitignore
+++ b/techlibs/xilinx/tests/.gitignore
@@ -12,4 +12,7 @@ test_dsp48a_model_ref.v
 test_dsp48a1_model_ref.v
 test_dsp48a1_model_uut.v
 test_dsp48a1_model
+test_dsp48_model_ref.v
+test_dsp48_model_uut.v
+test_dsp48_model
 *.vcd
diff --git a/techlibs/xilinx/tests/test_dsp48_model.sh b/techlibs/xilinx/tests/test_dsp48_model.sh
new file mode 100644
index 000000000..9a73f9b0c
--- /dev/null
+++ b/techlibs/xilinx/tests/test_dsp48_model.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -ex
+if [ -z $ISE_DIR ]; then
+	ISE_DIR=/opt/Xilinx/ISE/14.7
+fi
+sed 's/DSP48 /DSP48_UUT /; /DSP48_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp48_model_uut.v
+if [ ! -f "test_dsp48_model_ref.v" ]; then
+	cp $ISE_DIR/ISE_DS/ISE/verilog/src/unisims/DSP48.v test_dsp48_model_ref.v
+fi
+for tb in mult_allreg mult_noreg mult_inreg
+do
+	iverilog -s $tb -s glbl -o test_dsp48_model test_dsp48_model.v test_dsp48_model_uut.v test_dsp48_model_ref.v $ISE_DIR/ISE_DS/ISE/verilog/src/glbl.v
+	vvp -N ./test_dsp48_model
+done
diff --git a/techlibs/xilinx/tests/test_dsp48_model.v b/techlibs/xilinx/tests/test_dsp48_model.v
new file mode 100644
index 000000000..d69c00e93
--- /dev/null
+++ b/techlibs/xilinx/tests/test_dsp48_model.v
@@ -0,0 +1,287 @@
+`timescale 1ns / 1ps
+
+module testbench;
+	parameter integer AREG = 1;
+	parameter integer BREG = 1;
+	parameter integer CREG = 1;
+	parameter integer MREG = 1;
+	parameter integer PREG = 1;
+	parameter integer CARRYINREG = 1;
+	parameter integer CARRYINSELREG = 1;
+	parameter integer OPMODEREG = 1;
+	parameter integer SUBTRACTREG = 1;
+	parameter B_INPUT = "DIRECT";
+	parameter LEGACY_MODE = "NONE";
+
+	reg CLK;
+	reg CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL;
+	reg RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL;
+	reg [17:0] A;
+	reg [17:0] B;
+	reg [47:0] C;
+	reg [17:0] BCIN;
+	reg [47:0] PCIN;
+	reg CARRYIN;
+	reg [6:0] OPMODE;
+	reg SUBTRACT;
+	reg [1:0] CARRYINSEL;
+
+	output [47:0] P, REF_P;
+	output [17:0] BCOUT, REF_BCOUT;
+	output [47:0] PCOUT, REF_PCOUT;
+
+	integer errcount = 0;
+
+	reg ERROR_FLAG = 0;
+
+	task clkcycle;
+		begin
+			#5;
+			CLK = ~CLK;
+			#10;
+			CLK = ~CLK;
+			#2;
+			ERROR_FLAG = 0;
+			if (REF_BCOUT !== BCOUT) begin
+				$display("ERROR at %1t: REF_BCOUT=%b UUT_BCOUT=%b DIFF=%b", $time, REF_BCOUT, BCOUT, REF_BCOUT ^ BCOUT);
+				errcount = errcount + 1;
+				ERROR_FLAG = 1;
+			end
+			if (REF_P !== P) begin
+				$display("ERROR at %1t: REF_P=%b UUT_P=%b DIFF=%b", $time, REF_P, P, REF_P ^ P);
+				errcount = errcount + 1;
+				ERROR_FLAG = 1;
+			end
+			if (REF_PCOUT !== PCOUT) begin
+				$display("ERROR at %1t: REF_PCOUT=%b UUT_PCOUT=%b DIFF=%b", $time, REF_PCOUT, PCOUT, REF_PCOUT ^ PCOUT);
+				errcount = errcount + 1;
+				ERROR_FLAG = 1;
+			end
+			#3;
+		end
+	endtask
+
+	reg config_valid = 0;
+	task drc;
+		begin
+			config_valid = 1;
+
+			if (OPMODE[1:0] == 2'b10 && PREG != 1) config_valid = 0;
+			if (OPMODE[1:0] == 2'b00 && CARRYINSEL == 2'b10) config_valid = 0;
+			if (OPMODE[1:0] == 2'b10 && CARRYINSEL == 2'b10) config_valid = 0;
+			if (OPMODE[1:0] == 2'b00 && CARRYINSEL == 2'b11) config_valid = 0;
+			if (OPMODE[1:0] == 2'b10 && CARRYINSEL == 2'b11) config_valid = 0;
+			if (OPMODE[3:2] == 2'b10) config_valid = 0;
+			if ((OPMODE[3:2] == 2'b01) ^ (OPMODE[1:0] == 2'b01) == 1'b1) config_valid = 0;
+			if ((OPMODE[6:4] == 3'b010 || OPMODE[6:4] == 3'b110) && PREG != 1) config_valid = 0;
+			if (OPMODE[6:4] == 3'b100) config_valid = 0;
+			if (OPMODE[6:4] == 3'b111) config_valid = 0;
+			if (OPMODE[6:4] == 3'b000 && CARRYINSEL == 2'b01) config_valid = 0;
+			if (OPMODE[6:4] == 3'b011 && CARRYINSEL == 2'b01) config_valid = 0;
+
+			// Xilinx models consider these combinations invalid for an unknown reason.
+			if (CARRYINSEL == 2'b01 && OPMODE[3:2] == 2'b00) config_valid = 0;
+			if (CARRYINSEL == 2'b10 && OPMODE == 7'b0000011) config_valid = 0;
+			if (CARRYINSEL == 2'b10 && OPMODE == 7'b0000101) config_valid = 0;
+			if (CARRYINSEL == 2'b10 && OPMODE == 7'b0100011) config_valid = 0;
+			if (CARRYINSEL == 2'b10 && OPMODE == 7'b0111111) config_valid = 0;
+			if (CARRYINSEL == 2'b10 && OPMODE == 7'b1100011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0000011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0000101) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0011111) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0010011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0100011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0100101) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0101111) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0110011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b0111111) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b1010011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b1011111) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b1100011) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b1100101) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE == 7'b1101111) config_valid = 0;
+
+			if (CARRYINSEL == 2'b10 && OPMODE[3:0] == 4'b0101 && MREG == 1) config_valid = 0;
+			if (CARRYINSEL == 2'b11 && OPMODE[3:0] == 4'b0101 && MREG == 0) config_valid = 0;
+		end
+	endtask
+
+	initial begin
+		$dumpfile("test_dsp48_model.vcd");
+		$dumpvars(0, testbench);
+
+		#2;
+		CLK = 1'b0;
+		{CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL} = 8'b11111111;
+		{A, B, C, PCIN, OPMODE, SUBTRACT, CARRYIN, CARRYINSEL} = 0;
+		{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = 7'b1111111;
+		repeat (10) begin
+			#10;
+			CLK = 1'b1;
+			#10;
+			CLK = 1'b0;
+			#10;
+			CLK = 1'b1;
+			#10;
+			CLK = 1'b0;
+		end
+		{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = 0;
+
+		repeat (100000) begin
+			clkcycle;
+			config_valid = 0;
+			while (!config_valid) begin
+				A = $urandom;
+				B = $urandom;
+				C = {$urandom, $urandom};
+				BCIN = $urandom;
+				PCIN = {$urandom, $urandom};
+
+				{CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL} = $urandom | $urandom | $urandom;
+				{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = $urandom & $urandom & $urandom & $urandom & $urandom & $urandom;
+				{CARRYIN, CARRYINSEL, OPMODE, SUBTRACT} = $urandom;
+
+				drc;
+			end
+		end
+
+		if (errcount == 0) begin
+			$display("All tests passed.");
+			$finish;
+		end else begin
+			$display("Caught %1d errors.", errcount);
+			$stop;
+		end
+	end
+
+	DSP48 #(
+		.AREG               (AREG),
+		.BREG               (BREG),
+		.CREG               (CREG),
+		.MREG               (MREG),
+		.PREG               (PREG),
+		.CARRYINREG         (CARRYINREG),
+		.CARRYINSELREG      (CARRYINSELREG),
+		.OPMODEREG          (OPMODEREG),
+		.SUBTRACTREG        (SUBTRACTREG),
+		.B_INPUT            (B_INPUT),
+		.LEGACY_MODE        (LEGACY_MODE)
+	) ref (
+		.A             (A),
+		.B             (B),
+		.C             (C),
+		.BCIN          (BCIN),
+		.PCIN          (PCIN),
+		.CARRYIN       (CARRYIN),
+		.OPMODE        (OPMODE),
+		.SUBTRACT      (SUBTRACT),
+		.CARRYINSEL    (CARRYINSEL),
+		.BCOUT         (REF_BCOUT),
+		.P             (REF_P),
+		.PCOUT         (REF_PCOUT),
+		.CEA           (CEA),
+		.CEB           (CEB),
+		.CEC           (CEC),
+		.CEM           (CEM),
+		.CEP           (CEP),
+		.CECARRYIN     (CECARRYIN),
+		.CECINSUB       (CECINSUB),
+		.CECTRL        (CECTRL),
+		.CLK           (CLK),
+		.RSTA          (RSTA),
+		.RSTB          (RSTB),
+		.RSTC          (RSTC),
+		.RSTM          (RSTM),
+		.RSTP          (RSTP),
+		.RSTCARRYIN    (RSTCARRYIN),
+		.RSTCTRL       (RSTCTRL)
+	);
+
+	DSP48_UUT #(
+		.AREG               (AREG),
+		.BREG               (BREG),
+		.CREG               (CREG),
+		.MREG               (MREG),
+		.PREG               (PREG),
+		.CARRYINREG         (CARRYINREG),
+		.CARRYINSELREG      (CARRYINSELREG),
+		.OPMODEREG          (OPMODEREG),
+		.SUBTRACTREG        (SUBTRACTREG),
+		.B_INPUT            (B_INPUT),
+		.LEGACY_MODE        (LEGACY_MODE)
+	) uut (
+		.A             (A),
+		.B             (B),
+		.C             (C),
+		.BCIN          (BCIN),
+		.PCIN          (PCIN),
+		.CARRYIN       (CARRYIN),
+		.OPMODE        (OPMODE),
+		.SUBTRACT      (SUBTRACT),
+		.CARRYINSEL    (CARRYINSEL),
+		.BCOUT         (BCOUT),
+		.P             (P),
+		.PCOUT         (PCOUT),
+		.CEA           (CEA),
+		.CEB           (CEB),
+		.CEC           (CEC),
+		.CEM           (CEM),
+		.CEP           (CEP),
+		.CECARRYIN     (CECARRYIN),
+		.CECINSUB       (CECINSUB),
+		.CECTRL        (CECTRL),
+		.CLK           (CLK),
+		.RSTA          (RSTA),
+		.RSTB          (RSTB),
+		.RSTC          (RSTC),
+		.RSTM          (RSTM),
+		.RSTP          (RSTP),
+		.RSTCARRYIN    (RSTCARRYIN),
+		.RSTCTRL       (RSTCTRL)
+	);
+endmodule
+
+module mult_noreg;
+	testbench #(
+		.AREG               (0),
+		.BREG               (0),
+		.CREG               (0),
+		.MREG               (0),
+		.PREG               (0),
+		.CARRYINREG         (0),
+		.CARRYINSELREG      (0),
+		.OPMODEREG          (0),
+		.SUBTRACTREG        (0),
+		.B_INPUT            ("DIRECT")
+	) testbench ();
+endmodule
+
+module mult_allreg;
+	testbench #(
+		.AREG               (1),
+		.BREG               (1),
+		.CREG               (1),
+		.MREG               (1),
+		.PREG               (1),
+		.CARRYINREG         (1),
+		.CARRYINSELREG      (1),
+		.OPMODEREG          (1),
+		.SUBTRACTREG        (1),
+		.B_INPUT            ("CASCADE")
+	) testbench ();
+endmodule
+
+module mult_inreg;
+	testbench #(
+		.AREG               (1),
+		.BREG               (1),
+		.CREG               (1),
+		.MREG               (0),
+		.PREG               (0),
+		.CARRYINREG         (1),
+		.CARRYINSELREG      (0),
+		.OPMODEREG          (0),
+		.SUBTRACTREG        (0),
+		.B_INPUT            ("DIRECT")
+	) testbench ();
+endmodule
diff --git a/tests/arch/ecp5/opt_lut_ins.ys b/tests/arch/ecp5/opt_lut_ins.ys
new file mode 100644
index 000000000..2bc546912
--- /dev/null
+++ b/tests/arch/ecp5/opt_lut_ins.ys
@@ -0,0 +1,32 @@
+read_ilang << EOF
+
+module \top
+
+  wire input 1 \A
+  wire input 2 \B
+  wire input 3 \C
+  wire input 4 \D
+
+  wire output 5 \Z
+
+  cell \LUT4 $0
+    parameter \INIT 16'1111110011000000
+    connect \A \A
+    connect \B \B
+    connect \C \C
+    connect \D \D
+    connect \Z \Z
+  end
+end
+
+EOF
+
+read_verilog -lib +/ecp5/cells_sim.v
+
+equiv_opt -assert -map +/ecp5/cells_sim.v opt_lut_ins -tech ecp5
+
+design -load postopt
+
+select -assert-count 1 top/t:LUT4
+select -assert-count 0 top/w:A %co top/t:LUT4 %i
+select -assert-count 1 top/w:B %co top/t:LUT4 %i
diff --git a/tests/arch/efinix/mux.ys b/tests/arch/efinix/mux.ys
index a4268aea3..67006b6f2 100644
--- a/tests/arch/efinix/mux.ys
+++ b/tests/arch/efinix/mux.ys
@@ -36,6 +36,6 @@ proc
 equiv_opt -assert -map +/efinix/cells_sim.v synth_efinix # equivalency check
 design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design)
 cd mux16 # Constrain all select calls below inside the top module
-select -assert-count 11 t:EFX_LUT4
+select -assert-max 12 t:EFX_LUT4
 
 select -assert-none t:EFX_LUT4 %% t:* %D
diff --git a/tests/arch/gowin/mux.ys b/tests/arch/gowin/mux.ys
index afad29a89..33b092284 100644
--- a/tests/arch/gowin/mux.ys
+++ b/tests/arch/gowin/mux.ys
@@ -18,13 +18,13 @@ proc
 equiv_opt -assert -map +/gowin/cells_sim.v synth_gowin # equivalency check
 design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design)
 cd mux4 # Constrain all select calls below inside the top module
-select -assert-count 4 t:LUT4
+select -assert-count 4 t:LUT*
 select -assert-count 2 t:MUX2_LUT5
 select -assert-count 1 t:MUX2_LUT6
 select -assert-count 6 t:IBUF
 select -assert-count 1 t:OBUF
 
-select -assert-none t:LUT4 t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF %% t:* %D
+select -assert-none t:LUT* t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF %% t:* %D
 
 design -load read
 hierarchy -top mux8
@@ -35,7 +35,7 @@ cd mux8 # Constrain all select calls below inside the top module
 select -assert-count 11 t:IBUF
 select -assert-count 1 t:OBUF
 
-select -assert-none t:LUT4 t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF %% t:* %D
+select -assert-none t:LUT* t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF %% t:* %D
 
 design -load read
 hierarchy -top mux16
@@ -46,4 +46,4 @@ cd mux16 # Constrain all select calls below inside the top module
 select -assert-count 20 t:IBUF
 select -assert-count 1 t:OBUF
 
-select -assert-none t:LUT4 t:MUX2_LUT6 t:MUX2_LUT5 t:MUX2_LUT6 t:MUX2_LUT7 t:MUX2_LUT8 t:IBUF t:OBUF %% t:* %D
+select -assert-none t:GND t:VCC t:LUT* t:MUX2_LUT6 t:MUX2_LUT5 t:MUX2_LUT6 t:MUX2_LUT7 t:MUX2_LUT8 t:IBUF t:OBUF %% t:* %D
diff --git a/tests/arch/ice40/bug1597.ys b/tests/arch/ice40/bug1597.ys
new file mode 100644
index 000000000..b7983cfa4
--- /dev/null
+++ b/tests/arch/ice40/bug1597.ys
@@ -0,0 +1,72 @@
+read_verilog <<EOT
+module top (
+    input CLK, PIN_1, PIN_2, PIN_3, PIN_4, PIN_5,
+        PIN_6, PIN_7, PIN_8, PIN_9, PIN_10, PIN_11, PIN_12, PIN_13, PIN_25,
+    output USBPU, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18,
+        PIN_19, PIN_20, PIN_21, PIN_22, PIN_23, PIN_24,
+);
+    assign USBPU = 0;
+
+    wire[5:0] parOut;
+    wire[5:0] chrg;
+
+    assign PIN_14 = parOut[0];
+    assign PIN_15 = parOut[1];
+    assign PIN_16 = parOut[2];
+    assign PIN_17 = parOut[3];
+    assign PIN_18 = parOut[4];
+    assign PIN_19 = parOut[5];
+    assign chrg[0] = PIN_3;
+    assign chrg[1] = PIN_4;
+    assign chrg[2] = PIN_5;
+    assign chrg[3] = PIN_6;
+    assign chrg[4] = PIN_7;
+    assign chrg[5] = PIN_8;
+
+    SSCounter6o sc6(PIN_1, CLK, PIN_2, PIN_9, chrg, parOut);
+
+endmodule
+
+module SSCounter6 (input wire rst, clk, adv, jmp, input wire [5:0] in, output reg[5:0] out);
+    always @(posedge clk, posedge rst)
+        if (rst) out <= 0;
+        else if (adv || jmp) out <= jmp ? in : out + 1;
+endmodule
+
+// Optimized 6 bit counter, it should takes 7 cells.
+/* b[5:1]                       /* b[0]
+1010101010101010 in             1010101010101010 in
+1100110011001100 jmp            1100110011001100 jmp
+1111000011110000 loop           1111000011110000 loop
+1111111100000000 carry          1111111100000000 -
+----------------------          ----------------------
+1000101110111000 out            1000101110001011 out
+   8   B   B   8                   8   B   8   B
+*/
+module SSCounter6o (input wire rst, clk, adv, jmp, input wire [5:0] in, output wire[5:0] out);
+    wire[4:0] co;
+    wire[5:0] lo;
+    wire ien;
+    SB_LUT4 #(.LUT_INIT(16'hFFF0)) lien (ien, 0, 0, adv, jmp);
+    SB_CARRY c0 (co[0], jmp, out[0], 1),
+             c1 (co[1], jmp, out[1], co[0]),
+             c2 (co[2], jmp, out[2], co[1]),
+             c3 (co[3], jmp, out[3], co[2]),
+             c4 (co[4], jmp, out[4], co[3]);
+    SB_DFFER d0 (out[0], clk, ien, rst, lo[0]),
+             d1 (out[1], clk, ien, rst, lo[1]),
+             d2 (out[2], clk, ien, rst, lo[2]),
+             d3 (out[3], clk, ien, rst, lo[3]),
+             d4 (out[4], clk, ien, rst, lo[4]),
+             d5 (out[5], clk, ien, rst, lo[5]);
+    SB_LUT4 #(.LUT_INIT(16'h8B8B)) l0 (lo[0], in[0], jmp, out[0], 0);
+    SB_LUT4 #(.LUT_INIT(16'h8BB8)) l1 (lo[1], in[1], jmp, out[1], co[0]);
+    SB_LUT4 #(.LUT_INIT(16'h8BB8)) l2 (lo[2], in[2], jmp, out[2], co[1]);
+    SB_LUT4 #(.LUT_INIT(16'h8BB8)) l3 (lo[3], in[3], jmp, out[3], co[2]);
+    SB_LUT4 #(.LUT_INIT(16'h8BB8)) l4 (lo[4], in[4], jmp, out[4], co[3]);
+    SB_LUT4 #(.LUT_INIT(16'h8BB8)) l5 (lo[5], in[5], jmp, out[5], co[4]);
+endmodule
+EOT
+hierarchy -top top
+flatten
+equiv_opt -multiclock -map +/ice40/cells_sim.v synth_ice40
diff --git a/tests/arch/ice40/ice40_opt.ys b/tests/arch/ice40/ice40_opt.ys
index 5186d4800..71b68431e 100644
--- a/tests/arch/ice40/ice40_opt.ys
+++ b/tests/arch/ice40/ice40_opt.ys
@@ -1,23 +1,3 @@
-read_verilog -icells -formal <<EOT
-module \$__ICE40_CARRY_WRAPPER (output CO, O, input A, B, CI, I0, I3);
-  parameter LUT = 0;
-  SB_CARRY carry (
-    .I0(A),
-    .I1(B),
-    .CI(CI),
-    .CO(CO)
-  );
-  \$lut #(
-    .WIDTH(4),
-    .LUT(LUT)
-  ) lut (
-    .A({I0,A,B,I3}),
-    .Y(O)
-  );
-endmodule
-EOT
-design -stash unmap
-
 read_verilog -icells -formal <<EOT
 module top(input CI, I0, output [1:0] CO, output O);
     wire A = 1'b0, B = 1'b0;
@@ -26,13 +6,14 @@ module top(input CI, I0, output [1:0] CO, output O);
 		//    A[1]: 1100 1100 1100 1100
 		//    A[2]: 1111 0000 1111 0000
 		//    A[3]: 1111 1111 0000 0000
-		.LUT(~16'b 0110_1001_1001_0110)
+		.LUT(~16'b 0110_1001_1001_0110),
+		.I3_IS_CI(1'b1)
 	) u0 (
 		.A(A),
 		.B(B),
 		.CI(CI),
 		.I0(I0),
-		.I3(CI),
+		.I3(1'bx),
 		.CO(CO[0]),
 		.O(O)
 	);
@@ -40,7 +21,7 @@ module top(input CI, I0, output [1:0] CO, output O);
 endmodule
 EOT
 
-equiv_opt -assert -map %unmap -map +/ice40/cells_sim.v ice40_opt
+equiv_opt -assert -map +/ice40/abc9_model.v -map +/ice40/cells_sim.v ice40_opt
 design -load postopt
 select -assert-count 1 t:*
 select -assert-count 1 t:$lut
@@ -105,3 +86,33 @@ select -assert-count 1 t:SB_LUT4
 select -assert-count 1 t:SB_CARRY
 select -assert-count 1 t:SB_CARRY a:keep %i
 select -assert-count 1 t:SB_CARRY c:carry %i
+
+
+design -reset
+read_verilog -icells <<EOT
+module top(input I3, I2, I1, I0, output O, O2);
+	SB_LUT4 #(
+		.LUT_INIT(8'b 1001_0110)
+	) u0 (
+		.I0(I0),
+		.I1(I1),
+		.I2(I2),
+		.I3(),
+		.O(O)
+	);
+	wire CO;
+	\$__ICE40_CARRY_WRAPPER #(
+		.LUT(~8'b 1001_0110),
+		.I3_IS_CI(1'b0)
+	) u1 (
+		.A(1'b0),
+		.B(1'b0),
+		.CI(1'b0),
+		.I0(),
+		.I3(),
+		.CO(CO),
+		.O(O2)
+	);
+endmodule
+EOT
+ice40_opt
diff --git a/tests/arch/xilinx/opt_lut_ins.ys b/tests/arch/xilinx/opt_lut_ins.ys
new file mode 100644
index 000000000..a01d02179
--- /dev/null
+++ b/tests/arch/xilinx/opt_lut_ins.ys
@@ -0,0 +1,25 @@
+read_ilang << EOF
+
+module \top
+
+  wire width 4 input 1 \A
+
+  wire output 2 \O
+
+  cell \LUT4 $0
+    parameter \INIT 16'1111110011000000
+    connect \I0 \A [0]
+    connect \I1 \A [1]
+    connect \I2 \A [2]
+    connect \I3 \A [3]
+    connect \O \O
+  end
+end
+
+EOF
+
+equiv_opt -assert -map +/xilinx/cells_sim.v opt_lut_ins -tech xilinx
+
+design -load postopt
+
+select -assert-count 1 t:LUT3
diff --git a/tests/arch/xilinx/tribuf.sh b/tests/arch/xilinx/tribuf.sh
new file mode 100644
index 000000000..636aed12a
--- /dev/null
+++ b/tests/arch/xilinx/tribuf.sh
@@ -0,0 +1,5 @@
+! ../../../yosys ../common/tribuf.v -qp "synth_xilinx"
+../../../yosys ../common/tribuf.v -qp "synth_xilinx -iopad; \
+select -assert-count 2 t:IBUF; \
+select -assert-count 1 t:INV; \
+select -assert-count 1 t:OBUFT"
diff --git a/tests/opt/opt_lut_ins.ys b/tests/opt/opt_lut_ins.ys
new file mode 100644
index 000000000..82460b164
--- /dev/null
+++ b/tests/opt/opt_lut_ins.ys
@@ -0,0 +1,23 @@
+read_ilang << EOF
+
+module \top
+
+  wire width 4 input 1 \A
+
+  wire output 2 \Y
+
+  cell $lut \lut
+    parameter \LUT 16'1111110011000000
+    parameter \WIDTH 4
+    connect \A \A
+    connect \Y \Y
+  end
+end
+
+EOF
+
+equiv_opt -assert opt_lut_ins
+
+design -load postopt
+
+select -assert-count 1 t:$lut r:WIDTH=3 %i
diff --git a/tests/sat/clk2fflogic.ys b/tests/sat/clk2fflogic.ys
new file mode 100644
index 000000000..6d6d9e490
--- /dev/null
+++ b/tests/sat/clk2fflogic.ys
@@ -0,0 +1,66 @@
+read_verilog -icells <<EOT
+module top(input clk, d, s, r, output reg [17:0] q);
+always @(posedge clk or posedge s) if ( s) q[ 0] <= 1'b1; else q[ 0] <= d;
+always @(posedge clk or negedge s) if (!s) q[ 1] <= 1'b1; else q[ 1] <= d;
+always @(posedge clk or posedge r) if ( r) q[ 2] <= 1'b0; else q[ 2] <= d;
+always @(posedge clk or negedge r) if (!r) q[ 3] <= 1'b0; else q[ 3] <= d;
+always @(negedge clk or posedge s) if ( s) q[ 4] <= 1'b1; else q[ 4] <= d;
+always @(negedge clk or negedge s) if (!s) q[ 5] <= 1'b1; else q[ 5] <= d;
+always @(negedge clk or posedge r) if ( r) q[ 6] <= 1'b0; else q[ 6] <= d;
+always @(negedge clk or negedge r) if (!r) q[ 7] <= 1'b0; else q[ 7] <= d;
+
+// Seems like proc_dlatch always sets {SET,CLR}_POLARITY to true
+always @(posedge clk or posedge s or posedge r) if ( r) q[ 8] <= 1'b0; else if ( s) q[ 8] <= 1'b1; else q[ 8] <= d;
+//always @(posedge clk or posedge s or negedge r) if (!r) q[ 9] <= 1'b0; else if ( s) q[ 9] <= 1'b1; else q[ 9] <= d;
+//always @(posedge clk or negedge s or posedge r) if ( r) q[10] <= 1'b0; else if (!s) q[10] <= 1'b1; else q[10] <= d;
+//always @(posedge clk or negedge s or negedge r) if (!r) q[11] <= 1'b0; else if (!s) q[11] <= 1'b1; else q[11] <= d;
+$dffsr  #(.CLK_POLARITY(1'h1), .CLR_POLARITY(1'h0), .SET_POLARITY(1'h1), .WIDTH(32'd1)) ppn (.CLK(clk), .CLR(r), .D(d), .Q(q[ 9]), .SET(s));
+$dffsr  #(.CLK_POLARITY(1'h1), .CLR_POLARITY(1'h1), .SET_POLARITY(1'h0), .WIDTH(32'd1)) pnp (.CLK(clk), .CLR(r), .D(d), .Q(q[10]), .SET(s));
+$dffsr  #(.CLK_POLARITY(1'h1), .CLR_POLARITY(1'h0), .SET_POLARITY(1'h0), .WIDTH(32'd1)) pnn (.CLK(clk), .CLR(r), .D(d), .Q(q[11]), .SET(s));
+
+always @(negedge clk or posedge s or posedge r) if ( r) q[12] <= 1'b0; else if ( s) q[12] <= 1'b1; else q[12] <= d;
+//always @(negedge clk or posedge s or negedge r) if (!r) q[13] <= 1'b0; else if ( s) q[13] <= 1'b1; else q[13] <= d;
+//always @(negedge clk or negedge s or posedge r) if ( r) q[14] <= 1'b0; else if (!s) q[14] <= 1'b1; else q[14] <= d;
+//always @(negedge clk or negedge s or negedge r) if (!r) q[15] <= 1'b0; else if (!s) q[15] <= 1'b1; else q[15] <= d;
+$dffsr  #(.CLK_POLARITY(1'h0), .CLR_POLARITY(1'h0), .SET_POLARITY(1'h1), .WIDTH(32'd1)) npn (.CLK(clk), .CLR(r), .D(d), .Q(q[13]), .SET(s));
+$dffsr  #(.CLK_POLARITY(1'h0), .CLR_POLARITY(1'h1), .SET_POLARITY(1'h0), .WIDTH(32'd1)) nnp (.CLK(clk), .CLR(r), .D(d), .Q(q[14]), .SET(s));
+$dffsr  #(.CLK_POLARITY(1'h0), .CLR_POLARITY(1'h0), .SET_POLARITY(1'h0), .WIDTH(32'd1)) nnn (.CLK(clk), .CLR(r), .D(d), .Q(q[15]), .SET(s));
+
+always @(posedge clk) q[16] <= d;
+always @(negedge clk) q[17] <= d;
+endmodule
+EOT
+proc
+select -assert-count 8 t:$adff
+select -assert-count 8 t:$dffsr
+select -assert-count 2 t:$dff
+design -save gold
+
+simplemap
+select -assert-count 1 t:$_DFF_NN0_
+select -assert-count 1 t:$_DFF_NN1_
+select -assert-count 1 t:$_DFF_NP0_
+select -assert-count 1 t:$_DFF_NP1_
+select -assert-count 1 t:$_DFF_PN0_
+select -assert-count 1 t:$_DFF_PN1_
+select -assert-count 1 t:$_DFF_PP0_
+select -assert-count 1 t:$_DFF_PP1_
+stat
+select -assert-count 1 t:$_DFFSR_NNN_
+select -assert-count 1 t:$_DFFSR_NNP_
+select -assert-count 1 t:$_DFFSR_NPN_
+select -assert-count 1 t:$_DFFSR_NPP_
+select -assert-count 1 t:$_DFFSR_PNN_
+select -assert-count 1 t:$_DFFSR_PNP_
+select -assert-count 1 t:$_DFFSR_PPN_
+select -assert-count 1 t:$_DFFSR_PPP_
+select -assert-count 1 t:$_DFF_N_
+select -assert-count 1 t:$_DFF_P_
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+clk2fflogic
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+sat -verify -prove-asserts -show-ports -set-init-undef -seq 10 miter
diff --git a/tests/sat/initval.ys b/tests/sat/initval.ys
index 2079d2f34..1436724b0 100644
--- a/tests/sat/initval.ys
+++ b/tests/sat/initval.ys
@@ -2,3 +2,14 @@ read_verilog -sv initval.v
 proc;;
 
 sat -seq 10 -prove-asserts
+
+design -reset
+read_verilog -icells <<EOT
+module top(input clk, i, output [1:0] o);
+(* init = 2'bx0 *)
+wire [1:0] o;
+assign o[1] = o[0];
+$_DFF_P_ dff (.C(clk), .D(i), .Q(o[0]));
+endmodule
+EOT
+sat -seq 1
diff --git a/tests/simple_abc9/abc9.v b/tests/simple_abc9/abc9.v
index 8afd0ce96..e5837d480 100644
--- a/tests/simple_abc9/abc9.v
+++ b/tests/simple_abc9/abc9.v
@@ -213,7 +213,7 @@ module arbiter (clk, rst, request, acknowledge, grant, grant_valid, grant_encode
   input rst;
 endmodule
 
-(* abc_box_id=1 *)
+(* abc9_box_id=1, whitebox *)
 module MUXF8(input I0, I1, S, output O);
 endmodule
 
@@ -291,3 +291,19 @@ module abc9_test035(input clk, d, output reg [1:0] q);
 always @(posedge clk) q[0] <= d;
 always @(negedge clk) q[1] <= q[0];
 endmodule
+
+module abc9_test036(input A, B, S, output [1:0] O);
+  (* keep *)
+  MUXF8 m  (
+    .I0(I0),
+    .I1(I1),
+    .O(O[0]),
+    .S(S)
+  );
+  MUXF8 m2  (
+    .I0(I0),
+    .I1(I1),
+    .O(O[1]),
+    .S(S)
+  );
+endmodule
diff --git a/tests/simple_abc9/run-test.sh b/tests/simple_abc9/run-test.sh
index bc921daa9..32d7a80ca 100755
--- a/tests/simple_abc9/run-test.sh
+++ b/tests/simple_abc9/run-test.sh
@@ -28,4 +28,5 @@ exec ${MAKE:-make} -f ../tools/autotest.mk $seed *.v *.sv EXTRA_FLAGS="-n 300 -p
     abc9 -lut 4 -box ../abc.box; \
     clean; \
     check -assert; \
-    select -assert-none t:${DOLLAR}_NOT_ t:${DOLLAR}_AND_ %%'"
+    select -assert-none t:${DOLLAR}_NOT_ t:${DOLLAR}_AND_ %%; \
+    setattr -mod -unset whitebox'"
diff --git a/tests/techmap/abc9.ys b/tests/techmap/abc9.ys
index 62b5dfef6..2140dde26 100644
--- a/tests/techmap/abc9.ys
+++ b/tests/techmap/abc9.ys
@@ -39,6 +39,35 @@ design -load gold
 scratchpad -copy abc9.script.flow3 abc9.script
 abc9 -lut 4
 
+design -reset
+read_verilog <<EOT
+module top(input a, b, output o);
+(* keep *) wire w = a & b;
+assign o = ~w;
+endmodule
+EOT
+
+simplemap
+equiv_opt -assert abc9 -lut 4
+design -load postopt
+select -assert-count 2 t:$lut
+
+
+design -reset
+read_verilog -icells <<EOT
+module top(input a, b, output o);
+wire w;
+(* keep *) $_AND_ gate (.Y(w), .A(a), .B(b));
+assign o = ~w;
+endmodule
+EOT
+
+simplemap
+equiv_opt -assert abc9 -lut 4
+design -load postopt
+select -assert-count 1 t:$lut
+select -assert-count 1 t:$_AND_
+
 
 design -reset
 read_verilog -icells <<EOT
diff --git a/tests/various/abc9.ys b/tests/various/abc9.ys
index 81d0afd1b..0c7695089 100644
--- a/tests/various/abc9.ys
+++ b/tests/various/abc9.ys
@@ -14,6 +14,7 @@ design -import gate -as gate
 miter -equiv -flatten -make_assert -make_outputs gold gate miter
 sat -verify -prove-asserts -show-ports miter
 
+
 design -load read
 hierarchy -top abc9_test028
 proc
@@ -23,6 +24,7 @@ select -assert-count 1 t:$lut r:LUT=2'b01 r:WIDTH=1 %i %i
 select -assert-count 1 t:unknown
 select -assert-none t:$lut t:unknown %% t: %D
 
+
 design -load read
 hierarchy -top abc9_test032
 proc
@@ -38,3 +40,16 @@ design -import gate -as gate
 
 miter -equiv -flatten -make_assert -make_outputs gold gate miter
 sat -seq 10 -verify -prove-asserts -show-ports miter
+
+
+design -reset
+read_verilog -icells <<EOT
+module abc9_test036(input clk, d, output q);
+(* keep *) reg w;
+$__ABC9_FF_ ff(.D(d), .Q(w));
+wire \ff.clock = clk;
+wire \ff.init = 1'b0;
+assign q = w;
+endmodule
+EOT
+abc9 -lut 4 -dff
diff --git a/tests/various/help.ys b/tests/various/help.ys
new file mode 100644
index 000000000..9283ce8f1
--- /dev/null
+++ b/tests/various/help.ys
@@ -0,0 +1,2 @@
+help -all
+help -celltypes
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