diff --git a/CHANGELOG b/CHANGELOG
index f7774cb9e..c29429295 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -36,6 +36,8 @@ Yosys 0.9 .. Yosys 0.9-dev
     - Added "ice40_wrapcarry" to encapsulate SB_LUT+SB_CARRY pairs for techmapping
     - Removed "ice40_unlut"
     - Improvements in pmgen: slices, choices, define, generate
+    - Added "xilinx_srl" for Xilinx shift register extraction
+    - Removed "shregmap -tech xilinx" (superseded by "xilinx_srl")
 
 Yosys 0.8 .. Yosys 0.9
 ----------------------
diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc
index 8e0cbdca8..e73a7b1c9 100644
--- a/passes/pmgen/Makefile.inc
+++ b/passes/pmgen/Makefile.inc
@@ -30,3 +30,9 @@ PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg
 
 passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
 	$(P) mkdir -p passes/pmgen && python3 $< -o $@ -p peepopt $(filter-out $<,$^)
+
+# --------------------------------------
+
+OBJS += passes/pmgen/xilinx_srl.o
+passes/pmgen/xilinx_srl.o: passes/pmgen/xilinx_srl_pm.h
+$(eval $(call add_extra_objs,passes/pmgen/xilinx_srl_pm.h))
diff --git a/passes/pmgen/test_pmgen.cc b/passes/pmgen/test_pmgen.cc
index 0ad769dfd..4f3eec935 100644
--- a/passes/pmgen/test_pmgen.cc
+++ b/passes/pmgen/test_pmgen.cc
@@ -28,6 +28,7 @@ bool did_something;
 
 #include "passes/pmgen/test_pmgen_pm.h"
 #include "passes/pmgen/ice40_dsp_pm.h"
+#include "passes/pmgen/xilinx_srl_pm.h"
 #include "passes/pmgen/peepopt_pm.h"
 
 void reduce_chain(test_pmgen_pm &pm)
@@ -180,7 +181,7 @@ void generate_pattern(std::function<void(pm&,std::function<void()>)> run, const
 		while (modcnt < maxmodcnt && submodcnt < maxsubcnt && itercnt++ < 1000)
 		{
 			if (timeout++ > 10000)
-				log_error("pmgen generator is stuck: 10000 iterations an no matching module generated.\n");
+				log_error("pmgen generator is stuck: 10000 iterations with no matching module generated.\n");
 
 			pm matcher(mod, mod->cells());
 
@@ -216,7 +217,7 @@ void generate_pattern(std::function<void(pm&,std::function<void()>)> run, const
 			run(matcher, [](){});
 		}
 
-		if (submodcnt)
+		if (submodcnt && maxsubcnt < (1 << 16))
 			maxsubcnt *= 2;
 
 		design->remove(mod);
@@ -349,13 +350,18 @@ struct TestPmgenPass : public Pass {
 		if (pattern == "ice40_dsp")
 			return GENERATE_PATTERN(ice40_dsp_pm, ice40_dsp);
 
+		if (pattern == "xilinx_srl.fixed")
+			return GENERATE_PATTERN(xilinx_srl_pm, fixed);
+		if (pattern == "xilinx_srl.variable")
+			return GENERATE_PATTERN(xilinx_srl_pm, variable);
+
 		if (pattern == "peepopt-muldiv")
 			return GENERATE_PATTERN(peepopt_pm, muldiv);
 
 		if (pattern == "peepopt-shiftmul")
 			return GENERATE_PATTERN(peepopt_pm, shiftmul);
 
-		log_cmd_error("Unkown pattern: %s\n", pattern.c_str());
+		log_cmd_error("Unknown pattern: %s\n", pattern.c_str());
 	}
 
 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
diff --git a/passes/pmgen/xilinx_srl.cc b/passes/pmgen/xilinx_srl.cc
new file mode 100644
index 000000000..87fcaa15a
--- /dev/null
+++ b/passes/pmgen/xilinx_srl.cc
@@ -0,0 +1,262 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *            (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
+ *  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
+
+// for peepopt_pm
+bool did_something;
+
+#include "passes/pmgen/xilinx_srl_pm.h"
+#include "passes/pmgen/peepopt_pm.h"
+
+void run_fixed(xilinx_srl_pm &pm)
+{
+	auto &st = pm.st_fixed;
+	auto &ud = pm.ud_fixed;
+	log("Found fixed chain of length %d (%s):\n", GetSize(ud.longest_chain), log_id(st.first->type));
+
+	SigSpec initval;
+	for (auto cell : ud.longest_chain) {
+		log_debug("    %s\n", log_id(cell));
+		if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) {
+			SigBit Q = cell->getPort(ID(Q));
+			log_assert(Q.wire);
+			auto it = Q.wire->attributes.find(ID(init));
+			if (it != Q.wire->attributes.end()) {
+				auto &i = it->second[Q.offset];
+				initval.append(i);
+				i = State::Sx;
+			}
+			else
+				initval.append(State::Sx);
+		}
+		else if (cell->type.in(ID(FDRE), ID(FDRE_1))) {
+			if (cell->parameters.at(ID(INIT), State::S0).as_bool())
+				initval.append(State::S1);
+			else
+				initval.append(State::S0);
+		}
+		else
+			log_abort();
+		pm.autoremove(cell);
+	}
+
+	auto first_cell = ud.longest_chain.back();
+	auto last_cell = ud.longest_chain.front();
+	Cell *c = pm.module->addCell(NEW_ID, ID($__XILINX_SHREG_));
+	pm.module->swap_names(c, first_cell);
+
+	if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID(FDRE), ID(FDRE_1))) {
+		c->setParam(ID(DEPTH), GetSize(ud.longest_chain));
+		c->setParam(ID(INIT), initval.as_const());
+		if (first_cell->type.in(ID($_DFF_P_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+			c->setParam(ID(CLKPOL), 1);
+		else if (first_cell->type.in(ID($_DFF_N_), ID($DFFE_NN_), ID($_DFFE_NP_), ID(FDRE_1)))
+			c->setParam(ID(CLKPOL), 0);
+		else if (first_cell->type.in(ID(FDRE))) {
+			if (!first_cell->parameters.at(ID(IS_C_INVERTED), State::S0).as_bool())
+				c->setParam(ID(CLKPOL), 1);
+			else
+				c->setParam(ID(CLKPOL), 0);
+		}
+		else
+			log_abort();
+		if (first_cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_)))
+			c->setParam(ID(ENPOL), 1);
+		else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_PN_)))
+			c->setParam(ID(ENPOL), 0);
+		else
+			c->setParam(ID(ENPOL), 2);
+
+		c->setPort(ID(C), first_cell->getPort(ID(C)));
+		c->setPort(ID(D), first_cell->getPort(ID(D)));
+		c->setPort(ID(Q), last_cell->getPort(ID(Q)));
+		c->setPort(ID(L), GetSize(ud.longest_chain)-1);
+		if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_)))
+			c->setPort(ID(E), State::S1);
+		else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+			c->setPort(ID(E), first_cell->getPort(ID(E)));
+		else if (first_cell->type.in(ID(FDRE), ID(FDRE_1)))
+			c->setPort(ID(E), first_cell->getPort(ID(CE)));
+		else
+			log_abort();
+	}
+	else
+		log_abort();
+
+	log("    -> %s (%s)\n", log_id(c), log_id(c->type));
+}
+
+void run_variable(xilinx_srl_pm &pm)
+{
+	auto &st = pm.st_variable;
+	auto &ud = pm.ud_variable;
+
+	log("Found variable chain of length %d (%s):\n", GetSize(ud.chain), log_id(st.first->type));
+
+	SigSpec initval;
+	for (const auto &i : ud.chain) {
+		auto cell = i.first;
+		auto slice = i.second;
+		log_debug("    %s\n", log_id(cell));
+		if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID($dff), ID($dffe))) {
+			SigBit Q = cell->getPort(ID(Q))[slice];
+			log_assert(Q.wire);
+			auto it = Q.wire->attributes.find(ID(init));
+			if (it != Q.wire->attributes.end()) {
+				auto &i = it->second[Q.offset];
+				initval.append(i);
+				i = State::Sx;
+			}
+			else
+				initval.append(State::Sx);
+		}
+		else
+			log_abort();
+	}
+	pm.autoremove(st.shiftx);
+
+	auto first_cell = ud.chain.back().first;
+	auto first_slice = ud.chain.back().second;
+
+	Cell *c = pm.module->addCell(NEW_ID, ID($__XILINX_SHREG_));
+	pm.module->swap_names(c, first_cell);
+
+	if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID($dff), ID($dffe))) {
+		c->setParam(ID(DEPTH), GetSize(ud.chain));
+		c->setParam(ID(INIT), initval.as_const());
+		Const clkpol, enpol;
+		if (first_cell->type.in(ID($_DFF_P_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+			clkpol = 1;
+		else if (first_cell->type.in(ID($_DFF_N_), ID($DFFE_NN_), ID($_DFFE_NP_)))
+			clkpol = 0;
+		else if (first_cell->type.in(ID($dff), ID($dffe)))
+			clkpol = first_cell->getParam(ID(CLK_POLARITY));
+		else
+			log_abort();
+		if (first_cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_)))
+			enpol = 1;
+		else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_PN_)))
+			enpol = 0;
+		else if (first_cell->type.in(ID($dffe)))
+			enpol = first_cell->getParam(ID(EN_POLARITY));
+		else
+			enpol = 2;
+		c->setParam(ID(CLKPOL), clkpol);
+		c->setParam(ID(ENPOL), enpol);
+
+		if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+			c->setPort(ID(C), first_cell->getPort(ID(C)));
+		else if (first_cell->type.in(ID($dff), ID($dffe)))
+			c->setPort(ID(C), first_cell->getPort(ID(CLK)));
+		else
+			log_abort();
+		c->setPort(ID(D), first_cell->getPort(ID(D))[first_slice]);
+		c->setPort(ID(Q), st.shiftx->getPort(ID(Y)));
+		c->setPort(ID(L), st.shiftx->getPort(ID(B)));
+		if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($dff)))
+			c->setPort(ID(E), State::S1);
+		else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+			c->setPort(ID(E), first_cell->getPort(ID(E)));
+		else if (first_cell->type.in(ID($dffe)))
+			c->setPort(ID(E), first_cell->getPort(ID(EN)));
+		else
+			log_abort();
+	}
+	else
+		log_abort();
+
+	log("    -> %s (%s)\n", log_id(c), log_id(c->type));
+}
+
+struct XilinxSrlPass : public Pass {
+	XilinxSrlPass() : Pass("xilinx_srl", "Xilinx shift register extraction") { }
+	void help() YS_OVERRIDE
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    xilinx_srl [options] [selection]\n");
+		log("\n");
+		log("This pass converts chains of built-in flops (bit-level: $_DFF_[NP]_, $_DFFE_*\n");
+		log("and word-level: $dff, $dffe) as well as Xilinx flops (FDRE, FDRE_1) into a\n");
+		log("$__XILINX_SHREG cell. Chains must be of the same cell type, clock, clock polarity,\n");
+		log("enable, and enable polarity (where relevant).\n");
+		log("Flops with resets cannot be mapped to Xilinx devices and will not be inferred.");
+		log("\n");
+		log("    -minlen N\n");
+		log("        min length of shift register (default = 3)\n");
+		log("\n");
+		log("    -fixed\n");
+		log("        infer fixed-length shift registers.\n");
+		log("\n");
+		log("    -variable\n");
+		log("        infer variable-length shift registers (i.e. fixed-length shifts where\n");
+		log("        each element also fans-out to a $shiftx cell).\n");
+		log("\n");
+	}
+
+	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+	{
+		log_header(design, "Executing XILINX_SRL pass (Xilinx shift register extraction).\n");
+
+		bool fixed = false;
+		bool variable = false;
+		int minlen = 3;
+
+		size_t argidx;
+		for (argidx = 1; argidx < args.size(); argidx++)
+		{
+			if (args[argidx] == "-minlen" && argidx+1 < args.size()) {
+				minlen = atoi(args[++argidx].c_str());
+				continue;
+			}
+			if (args[argidx] == "-fixed") {
+				fixed = true;
+				continue;
+			}
+			if (args[argidx] == "-variable") {
+				variable = true;
+				continue;
+			}
+			break;
+		}
+		extra_args(args, argidx, design);
+
+		if (!fixed && !variable)
+			log_cmd_error("'-fixed' and/or '-variable' must be specified.\n");
+
+		for (auto module : design->selected_modules()) {
+			auto pm = xilinx_srl_pm(module, module->selected_cells());
+			pm.ud_fixed.minlen = minlen;
+			pm.ud_variable.minlen = minlen;
+
+			if (fixed)
+				pm.run_fixed(run_fixed);
+			if (variable)
+				pm.run_variable(run_variable);
+		}
+	}
+} XilinxSrlPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/pmgen/xilinx_srl.pmg b/passes/pmgen/xilinx_srl.pmg
new file mode 100644
index 000000000..45d44247a
--- /dev/null
+++ b/passes/pmgen/xilinx_srl.pmg
@@ -0,0 +1,326 @@
+pattern fixed
+
+state <IdString> clk_port en_port
+udata <vector<Cell*>> chain longest_chain
+udata <pool<Cell*>> non_first_cells
+udata <int> minlen
+
+code
+	non_first_cells.clear();
+	subpattern(setup);
+endcode
+
+match first
+	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
+	select !first->has_keep_attr()
+	select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
+	select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
+	select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero()
+	filter !non_first_cells.count(first)
+generate
+	SigSpec C = module->addWire(NEW_ID);
+	SigSpec D = module->addWire(NEW_ID);
+	SigSpec Q = module->addWire(NEW_ID);
+	auto r = rng(8);
+	Cell* cell;
+	switch (r)
+	{
+	case 0:
+	case 1:
+		cell = module->addCell(NEW_ID, \FDRE);
+		cell->setPort(\C, C);
+		cell->setPort(\D, D);
+		cell->setPort(\Q, Q);
+		cell->setPort(\CE, module->addWire(NEW_ID));
+		if (r & 1)
+			cell->setPort(\R, module->addWire(NEW_ID));
+		else {
+			if (rng(2) == 0)
+				cell->setPort(\R, State::S0);
+		}
+		break;
+	case 2:
+	case 3:
+		cell = module->addDffGate(NEW_ID, C, D, Q, r & 1);
+		break;
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		cell = module->addDffeGate(NEW_ID, C, module->addWire(NEW_ID), D, Q, r & 1, r & 2);
+		break;
+	default: log_abort();
+	}
+endmatch
+
+code clk_port en_port
+	if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1))
+		clk_port = \C;
+	else log_abort();
+	if (first->type.in($_DFF_N_, $_DFF_P_))
+		en_port = IdString();
+	else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_))
+		en_port = \E;
+	else if (first->type.in(\FDRE, \FDRE_1))
+		en_port = \CE;
+	else log_abort();
+
+	longest_chain.clear();
+	chain.push_back(first);
+	subpattern(tail);
+finally
+	chain.pop_back();
+	log_assert(chain.empty());
+	if (GetSize(longest_chain) >= minlen)
+		accept;
+endcode
+
+// ------------------------------------------------------------------
+
+subpattern setup
+arg clk_port
+arg en_port
+
+match first
+	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
+	select !first->has_keep_attr()
+	select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
+	select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
+	select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero()
+endmatch
+
+code clk_port en_port
+	if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1))
+		clk_port = \C;
+	else log_abort();
+	if (first->type.in($_DFF_N_, $_DFF_P_))
+		en_port = IdString();
+	else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_))
+		en_port = \E;
+	else if (first->type.in(\FDRE, \FDRE_1))
+		en_port = \CE;
+	else log_abort();
+endcode
+
+match next
+	select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
+	select !next->has_keep_attr()
+	select !port(next, \D)[0].wire->get_bool_attribute(\keep)
+	select nusers(port(next, \Q)) == 2
+	index <IdString> next->type === first->type
+	index <SigBit> port(next, \Q) === port(first, \D)
+	filter port(next, clk_port) == port(first, clk_port)
+	filter en_port == IdString() || port(next, en_port) == port(first, en_port)
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero()
+endmatch
+
+code
+	non_first_cells.insert(next);
+endcode
+
+// ------------------------------------------------------------------
+
+subpattern tail
+arg first
+arg clk_port
+arg en_port
+
+match next
+	semioptional
+	select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
+	select !next->has_keep_attr()
+	select !port(next, \D)[0].wire->get_bool_attribute(\keep)
+	select nusers(port(next, \Q)) == 2
+	index <IdString> next->type === chain.back()->type
+	index <SigBit> port(next, \Q) === port(chain.back(), \D)
+	filter port(next, clk_port) == port(first, clk_port)
+	filter en_port == IdString() || port(next, en_port) == port(first, en_port)
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
+	filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero()
+generate
+	Cell *cell = module->addCell(NEW_ID, chain.back()->type);
+	cell->setPort(\C, chain.back()->getPort(\C));
+	cell->setPort(\D, module->addWire(NEW_ID));
+	cell->setPort(\Q, chain.back()->getPort(\D));
+	if (cell->type == \FDRE) {
+		if (rng(2) == 0)
+			cell->setPort(\R, chain.back()->connections_.at(\R, State::S0));
+		cell->setPort(\CE, chain.back()->getPort(\CE));
+	}
+	else if (cell->type.begins_with("$_DFFE_"))
+		cell->setPort(\E, chain.back()->getPort(\E));
+endmatch
+
+code
+	if (next) {
+		chain.push_back(next);
+		subpattern(tail);
+	} else {
+		if (GetSize(chain) > GetSize(longest_chain))
+			longest_chain = chain;
+	}
+finally
+	if (next)
+		chain.pop_back();
+endcode
+
+// -----------
+
+pattern variable
+
+state <IdString> clk_port en_port
+state <int> shiftx_width
+state <int> slice
+udata <int> minlen
+udata <vector<pair<Cell*,int>>> chain
+udata <pool<SigBit>> chain_bits
+
+code
+	chain_bits.clear();
+endcode
+
+match shiftx
+	select shiftx->type.in($shiftx)
+	select !shiftx->has_keep_attr()
+	select param(shiftx, \Y_WIDTH).as_int() == 1
+	filter param(shiftx, \A_WIDTH).as_int() >= minlen
+generate
+	minlen = 3;
+	module->addShiftx(NEW_ID, module->addWire(NEW_ID, rng(6)+minlen), module->addWire(NEW_ID, 3), module->addWire(NEW_ID));
+endmatch
+
+code shiftx_width
+	shiftx_width = param(shiftx, \A_WIDTH).as_int();
+endcode
+
+match first
+	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, $dff, $dffe)
+	select !first->has_keep_attr()
+	select !port(first, \Q)[0].wire->get_bool_attribute(\keep)
+	slice idx GetSize(port(first, \Q))
+	select nusers(port(first, \Q)[idx]) <= 2
+	index <SigBit> port(first, \Q)[idx] === port(shiftx, \A)[shiftx_width-1]
+	set slice idx
+generate
+	SigSpec C = module->addWire(NEW_ID);
+	auto WIDTH = rng(3)+1;
+	SigSpec D = module->addWire(NEW_ID, WIDTH);
+	SigSpec Q = module->addWire(NEW_ID, WIDTH);
+	auto r = rng(8);
+	Cell *cell = nullptr;
+	switch (r)
+	{
+	case 0:
+	case 1:
+		cell = module->addDff(NEW_ID, C, D, Q, r & 1);
+		break;
+	case 2:
+	case 3:
+	case 4:
+	case 5:
+		//cell = module->addDffe(NEW_ID, C, module->addWire(NEW_ID), D, Q, r & 1, r & 4);
+		//break;
+	case 6:
+	case 7:
+		WIDTH = 1;
+		cell = module->addDffGate(NEW_ID, C, D[0], Q[0], r & 1);
+		break;
+	default: log_abort();
+	}
+	shiftx->connections_.at(\A)[shiftx_width-1] = port(cell, \Q)[rng(WIDTH)];
+endmatch
+
+code clk_port en_port
+	if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_))
+		clk_port = \C;
+	else if (first->type.in($dff, $dffe))
+		clk_port = \CLK;
+	else log_abort();
+	if (first->type.in($_DFF_N_, $_DFF_P_, $dff))
+		en_port = IdString();
+	else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_))
+		en_port = \E;
+	else if (first->type.in($dffe))
+		en_port = \EN;
+	else log_abort();
+
+	chain_bits.insert(port(first, \Q)[slice]);
+	chain.emplace_back(first, slice);
+	subpattern(tail);
+finally
+	if (GetSize(chain) == shiftx_width)
+		accept;
+	chain.clear();
+endcode
+
+// ------------------------------------------------------------------
+
+subpattern tail
+arg first
+arg shiftx
+arg shiftx_width
+arg slice
+arg clk_port
+arg en_port
+
+match next
+	semioptional
+	select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, $dff, $dffe)
+	select !next->has_keep_attr()
+	select !port(next, \D)[0].wire->get_bool_attribute(\keep)
+	slice idx GetSize(port(next, \Q))
+	select nusers(port(next, \Q)[idx]) <= 3
+	index <IdString> next->type === chain.back().first->type
+	index <SigBit> port(next, \Q)[idx] === port(chain.back().first, \D)[chain.back().second]
+	index <SigBit> port(next, \Q)[idx] === port(shiftx, \A)[shiftx_width-1-GetSize(chain)]
+	filter port(next, clk_port) == port(first, clk_port)
+	filter en_port == IdString() || port(next, en_port) == port(first, en_port)
+	filter !next->type.in($dff, $dffe) || param(next, \CLK_POLARITY).as_bool() == param(first, \CLK_POLARITY).as_bool()
+	filter !next->type.in($dffe) || param(next, \EN_POLARITY).as_bool() == param(first, \EN_POLARITY).as_bool()
+	filter !chain_bits.count(port(next, \D)[idx])
+	set slice idx
+generate
+	if (GetSize(chain) < shiftx_width) {
+		auto back = chain.back().first;
+		auto slice = chain.back().second;
+		if (back->type.in($dff, $dffe)) {
+			auto WIDTH = GetSize(port(back, \D));
+			if (rng(2) == 0 && slice < WIDTH-1) {
+				auto new_slice = slice + rng(WIDTH-1-slice);
+				back->connections_.at(\D)[slice] = port(back, \Q)[new_slice];
+			}
+			else {
+				auto D = module->addWire(NEW_ID, WIDTH);
+				if (back->type == $dff)
+					module->addDff(NEW_ID, port(back, \CLK), D, port(back, \D), param(back, \CLK_POLARITY).as_bool());
+				else if (back->type == $dffe)
+					module->addDffe(NEW_ID, port(back, \CLK), port(back, \EN), D, port(back, \D), param(back, \CLK_POLARITY).as_bool(), param(back, \EN_POLARITY).as_bool());
+				else
+					log_abort();
+			}
+		}
+		else if (back->type.begins_with("$_DFF_")) {
+			Cell *cell = module->addCell(NEW_ID, back->type);
+			cell->setPort(\C, back->getPort(\C));
+			cell->setPort(\D, module->addWire(NEW_ID));
+			cell->setPort(\Q, back->getPort(\D));
+		}
+		else
+			log_abort();
+		shiftx->connections_.at(\A)[shiftx_width-1-GetSize(chain)] = port(back, \D)[slice];
+	}
+endmatch
+
+code
+	if (next) {
+		chain_bits.insert(port(next, \Q)[slice]);
+		chain.emplace_back(next, slice);
+		if (GetSize(chain) < shiftx_width)
+			subpattern(tail);
+	}
+endcode
diff --git a/passes/techmap/shregmap.cc b/passes/techmap/shregmap.cc
index 5e298d8dd..9da69e8ba 100644
--- a/passes/techmap/shregmap.cc
+++ b/passes/techmap/shregmap.cc
@@ -26,9 +26,7 @@ PRIVATE_NAMESPACE_BEGIN
 struct ShregmapTech
 {
 	virtual ~ShregmapTech() { }
-	virtual void init(const Module * /*module*/, const SigMap &/*sigmap*/) {}
-	virtual void non_chain_user(const SigBit &/*bit*/, const Cell* /*cell*/, IdString /*port*/) {}
-	virtual bool analyze(vector<int> &taps, const vector<SigBit> &qbits) = 0;
+	virtual bool analyze(vector<int> &taps) = 0;
 	virtual bool fixup(Cell *cell, dict<int, SigBit> &taps) = 0;
 };
 
@@ -56,7 +54,7 @@ struct ShregmapOptions
 
 struct ShregmapTechGreenpak4 : ShregmapTech
 {
-	bool analyze(vector<int> &taps, const vector<SigBit> &/*qbits*/)
+	bool analyze(vector<int> &taps)
 	{
 		if (GetSize(taps) > 2 && taps[0] == 0 && taps[2] < 17) {
 			taps.clear();
@@ -93,155 +91,6 @@ struct ShregmapTechGreenpak4 : ShregmapTech
 	}
 };
 
-struct ShregmapTechXilinx7 : ShregmapTech
-{
-	dict<SigBit, std::tuple<Cell*,int,int>> sigbit_to_shiftx_offset;
-	const ShregmapOptions &opts;
-
-	ShregmapTechXilinx7(const ShregmapOptions &opts) : opts(opts) {}
-
-	virtual void init(const Module* module, const SigMap &sigmap) override
-	{
-		for (const auto &i : module->cells_) {
-			auto cell = i.second;
-			if (cell->type == ID($shiftx)) {
-				if (cell->getParam(ID(Y_WIDTH)) != 1) continue;
-				int j = 0;
-				for (auto bit : sigmap(cell->getPort(ID::A)))
-					sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, j++, 0);
-				log_assert(j == cell->getParam(ID(A_WIDTH)).as_int());
-			}
-			else if (cell->type == ID($mux)) {
-				int j = 0;
-				for (auto bit : sigmap(cell->getPort(ID::A)))
-					sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 0, j++);
-				j = 0;
-				for (auto bit : sigmap(cell->getPort(ID::B)))
-					sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 1, j++);
-			}
-		}
-	}
-
-	virtual void non_chain_user(const SigBit &bit, const Cell *cell, IdString port) override
-	{
-		auto it = sigbit_to_shiftx_offset.find(bit);
-		if (it == sigbit_to_shiftx_offset.end())
-			return;
-		if (cell) {
-			if (cell->type == ID($shiftx) && port == ID::A)
-				return;
-			if (cell->type == ID($mux) && port.in(ID::A, ID::B))
-				return;
-		}
-		sigbit_to_shiftx_offset.erase(it);
-	}
-
-	virtual bool analyze(vector<int> &taps, const vector<SigBit> &qbits) override
-	{
-		if (GetSize(taps) == 1)
-			return taps[0] >= opts.minlen-1 && sigbit_to_shiftx_offset.count(qbits[0]);
-
-		if (taps.back() < opts.minlen-1)
-			return false;
-
-		Cell *shiftx = nullptr;
-		int group = 0;
-		for (int i = 0; i < GetSize(taps); ++i) {
-			auto it = sigbit_to_shiftx_offset.find(qbits[i]);
-			if (it == sigbit_to_shiftx_offset.end())
-				return false;
-
-			// Check taps are sequential
-			if (i != taps[i])
-				return false;
-			// Check taps are not connected to a shift register,
-			// or sequential to the same shift register
-			if (i == 0) {
-				int offset;
-				std::tie(shiftx,offset,group) = it->second;
-				if (offset != i)
-					return false;
-			}
-			else {
-				Cell *shiftx_ = std::get<0>(it->second);
-				if (shiftx_ != shiftx)
-					return false;
-				int offset = std::get<1>(it->second);
-				if (offset != i)
-					return false;
-				int group_ = std::get<2>(it->second);
-				if (group_ != group)
-					return false;
-			}
-		}
-		log_assert(shiftx);
-
-		// Only map if $shiftx exclusively covers the shift register
-		if (shiftx->type == ID($shiftx)) {
-			if (GetSize(taps) > shiftx->getParam(ID(A_WIDTH)).as_int())
-				return false;
-			// Due to padding the most significant bits of A may be 1'bx,
-			//   and if so, discount them
-			if (GetSize(taps) < shiftx->getParam(ID(A_WIDTH)).as_int()) {
-				const SigSpec A = shiftx->getPort(ID::A);
-				const int A_width = shiftx->getParam(ID(A_WIDTH)).as_int();
-				for (int i = GetSize(taps); i < A_width; ++i)
-					if (A[i] != RTLIL::Sx) return false;
-			}
-			else if (GetSize(taps) != shiftx->getParam(ID(A_WIDTH)).as_int())
-				return false;
-		}
-		else if (shiftx->type == ID($mux)) {
-			if (GetSize(taps) != 2)
-				return false;
-		}
-		else log_abort();
-
-		return true;
-	}
-
-	virtual bool fixup(Cell *cell, dict<int, SigBit> &taps) override
-	{
-		const auto &tap = *taps.begin();
-		auto bit = tap.second;
-
-		auto it = sigbit_to_shiftx_offset.find(bit);
-		log_assert(it != sigbit_to_shiftx_offset.end());
-
-		auto newcell = cell->module->addCell(NEW_ID, ID($__XILINX_SHREG_));
-		newcell->set_src_attribute(cell->get_src_attribute());
-		newcell->setParam(ID(DEPTH), cell->getParam(ID(DEPTH)));
-		newcell->setParam(ID(INIT), cell->getParam(ID(INIT)));
-		newcell->setParam(ID(CLKPOL), cell->getParam(ID(CLKPOL)));
-		newcell->setParam(ID(ENPOL), cell->getParam(ID(ENPOL)));
-
-		newcell->setPort(ID(C), cell->getPort(ID(C)));
-		newcell->setPort(ID(D), cell->getPort(ID(D)));
-		if (cell->hasPort(ID(E)))
-			newcell->setPort(ID(E), cell->getPort(ID(E)));
-
-		Cell* shiftx = std::get<0>(it->second);
-		RTLIL::SigSpec l_wire, q_wire;
-		if (shiftx->type == ID($shiftx)) {
-			l_wire = shiftx->getPort(ID::B);
-			q_wire = shiftx->getPort(ID::Y);
-			shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID));
-		}
-		else if (shiftx->type == ID($mux)) {
-			l_wire = shiftx->getPort(ID(S));
-			q_wire = shiftx->getPort(ID::Y);
-			shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID));
-		}
-		else log_abort();
-
-		newcell->setPort(ID(Q), q_wire);
-		newcell->setPort(ID(L), l_wire);
-
-		return false;
-	}
-};
-
-
 struct ShregmapWorker
 {
 	Module *module;
@@ -264,10 +113,8 @@ struct ShregmapWorker
 		for (auto wire : module->wires())
 		{
 			if (wire->port_output || wire->get_bool_attribute(ID::keep)) {
-				for (auto bit : sigmap(wire)) {
+				for (auto bit : sigmap(wire))
 					sigbit_with_non_chain_users.insert(bit);
-					if (opts.tech) opts.tech->non_chain_user(bit, nullptr, {});
-				}
 			}
 
 			if (wire->attributes.count(ID(init))) {
@@ -317,10 +164,8 @@ struct ShregmapWorker
 
 			for (auto conn : cell->connections())
 				if (cell->input(conn.first))
-					for (auto bit : sigmap(conn.second)) {
+					for (auto bit : sigmap(conn.second))
 						sigbit_with_non_chain_users.insert(bit);
-						if (opts.tech) opts.tech->non_chain_user(bit, cell, conn.first);
-					}
 		}
 	}
 
@@ -425,7 +270,7 @@ struct ShregmapWorker
 					if (taps.empty() || taps.back() < depth-1)
 						taps.push_back(depth-1);
 
-					if (opts.tech->analyze(taps, qbits))
+					if (opts.tech->analyze(taps))
 						break;
 
 					taps.pop_back();
@@ -544,9 +389,6 @@ struct ShregmapWorker
 	ShregmapWorker(Module *module, const ShregmapOptions &opts) :
 			module(module), sigmap(module), opts(opts), dff_count(0), shreg_count(0)
 	{
-		if (opts.tech)
-			opts.tech->init(module, sigmap);
-
 		make_sigbit_chain_next_prev();
 		find_chain_start_cells();
 
@@ -617,11 +459,6 @@ struct ShregmapPass : public Pass {
 		log("\n");
 		log("    -tech greenpak4\n");
 		log("        map to greenpak4 shift registers.\n");
-		log("        this option also implies -clkpol pos -zinit\n");
-		log("\n");
-		log("    -tech xilinx\n");
-		log("        map to xilinx dynamic-length shift registers.\n");
-		log("        this option also implies -params -init\n");
 		log("\n");
 	}
 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
@@ -676,12 +513,6 @@ struct ShregmapPass : public Pass {
 					clkpol = "pos";
 					opts.zinit = true;
 					opts.tech = new ShregmapTechGreenpak4;
-				}
-				else if (tech == "xilinx") {
-					opts.init = true;
-					opts.params = true;
-					enpol = "any_or_none";
-					opts.tech = new ShregmapTechXilinx7(opts);
 				} else {
 					argidx--;
 					break;
diff --git a/techlibs/xilinx/synth_xilinx.cc b/techlibs/xilinx/synth_xilinx.cc
index db8c9fa31..bfc0ac2bf 100644
--- a/techlibs/xilinx/synth_xilinx.cc
+++ b/techlibs/xilinx/synth_xilinx.cc
@@ -303,9 +303,8 @@ struct SynthXilinxPass : public ScriptPass
 			if (widemux > 0 || help_mode)
 				run("muxpack", "    ('-widemux' only)");
 
-			// shregmap -tech xilinx can cope with $shiftx and $mux
-			//   cells for identifying variable-length shift registers,
-			//   so attempt to convert $pmux-es to the former
+			// xilinx_srl looks for $shiftx cells for identifying variable-length
+			//   shift registers, so attempt to convert $pmux-es to this
 			// Also: wide multiplexer inference benefits from this too
 			if (!(nosrl && widemux == 0) || help_mode) {
 				run("pmux2shiftx", "(skip if '-nosrl' and '-widemux=0')");
@@ -387,13 +386,8 @@ struct SynthXilinxPass : public ScriptPass
 			}
 			run("opt -full");
 
-			if (!nosrl || help_mode) {
-				// shregmap operates on bit-level flops, not word-level,
-				//   so break those down here
-				run("simplemap t:$dff t:$dffe", "       (skip if '-nosrl')");
-				// shregmap with '-tech xilinx' infers variable length shift regs
-				run("shregmap -tech xilinx -minlen 3", "(skip if '-nosrl')");
-			}
+			if (!nosrl || help_mode)
+				run("xilinx_srl -variable -minlen 3", "(skip if '-nosrl')");
 
 			std::string techmap_args = " -map +/techmap.v";
 			if (help_mode)
@@ -421,6 +415,14 @@ struct SynthXilinxPass : public ScriptPass
 			run("clean");
 		}
 
+		if (check_label("map_ffs")) {
+				if (abc9 || help_mode) {
+						run("techmap -map +/xilinx/ff_map.v", "('-abc9' only)");
+						run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT "
+										"-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT", "('-abc9' only)");
+				}
+		}
+
 		if (check_label("map_luts")) {
 			run("opt_expr -mux_undef");
 			if (flatten_before_abc)
@@ -448,13 +450,18 @@ struct SynthXilinxPass : public ScriptPass
 			// This shregmap call infers fixed length shift registers after abc
 			//   has performed any necessary retiming
 			if (!nosrl || help_mode)
-				run("shregmap -minlen 3 -init -params -enpol any_or_none", "(skip if '-nosrl')");
-			std::string techmap_args = "-map +/xilinx/lut_map.v -map +/xilinx/ff_map.v";
-			if (abc9)
+				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 += " [-map +/xilinx/ff_map.v]";
+			else if (abc9)
 				techmap_args += " -map +/xilinx/abc_unmap.v";
+			else
+				techmap_args += " -map +/xilinx/ff_map.v";
 			run("techmap " + techmap_args);
-			run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT "
-					"-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT");
+			if (!abc9 || help_mode)
+				run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT "
+						"-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT", "(without '-abc9' only)");
 			run("clean");
 		}
 
diff --git a/tests/various/shregmap.ys b/tests/various/shregmap.ys
index 0e5fe882b..16e5f40e1 100644
--- a/tests/various/shregmap.ys
+++ b/tests/various/shregmap.ys
@@ -31,36 +31,3 @@ sat -verify -prove-asserts -show-ports -seq 5 miter
 
 #design -load gate
 #stat
-
-##########
-
-design -load read
-design -copy-to model $__XILINX_SHREG_
-hierarchy -top shregmap_variable_test
-prep
-design -save gold
-
-simplemap t:$dff t:$dffe
-shregmap -tech xilinx
-
-#stat
-# show -width
-# write_verilog -noexpr -norename
-select -assert-count 1 t:$_DFF_P_
-select -assert-count 2 t:$__XILINX_SHREG_
-
-design -stash gate
-
-design -import gold -as gold
-design -import gate -as gate
-design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_
-prep
-
-miter -equiv -flatten -make_assert -make_outputs gold gate miter
-sat -verify -prove-asserts -show-ports -seq 5 miter
-
-# design -load gold
-# stat
-
-# design -load gate
-# stat
diff --git a/tests/xilinx/.gitignore b/tests/xilinx/.gitignore
new file mode 100644
index 000000000..b48f808a1
--- /dev/null
+++ b/tests/xilinx/.gitignore
@@ -0,0 +1,3 @@
+/*.log
+/*.out
+/run-test.mk
diff --git a/tests/xilinx/pmgen_xilinx_srl.ys b/tests/xilinx/pmgen_xilinx_srl.ys
new file mode 100644
index 000000000..ea2f20487
--- /dev/null
+++ b/tests/xilinx/pmgen_xilinx_srl.ys
@@ -0,0 +1,57 @@
+read_verilog -icells <<EOT
+module \$__XILINX_SHREG_ (input C, input D, input [31:0] L, input E, output Q, output SO);
+  parameter DEPTH = 1;
+  parameter [DEPTH-1:0] INIT = 0;
+  parameter CLKPOL = 1;
+  parameter ENPOL = 2;
+
+  wire pos_clk = C == CLKPOL;
+  reg pos_en;
+  always @(E)
+    if (ENPOL == 2) pos_en = 1'b1;
+    else pos_en = (E == ENPOL[0]);
+
+  reg [DEPTH-1:0] r;
+  always @(posedge pos_clk)
+    if (pos_en)
+      r <= {r[DEPTH-2:0], D};
+
+  assign Q = r[L];
+  assign SO = r[DEPTH-1];
+endmodule
+EOT
+read_verilog +/xilinx/cells_sim.v
+proc
+design -save model
+
+test_pmgen -generate xilinx_srl.fixed
+hierarchy -top pmtest_xilinx_srl_pm_fixed
+flatten; opt_clean
+
+design -save gold
+xilinx_srl -fixed
+techmap -autoproc -map %model
+design -stash gate
+
+design -copy-from gold -as gold pmtest_xilinx_srl_pm_fixed
+design -copy-from gate -as gate pmtest_xilinx_srl_pm_fixed
+dff2dffe -unmap # sat does not support flops-with-enable yet
+miter -equiv -flatten -make_assert gold gate miter
+sat -set-init-zero -seq 5 -verify -prove-asserts miter
+
+design -load model
+
+test_pmgen -generate xilinx_srl.variable
+hierarchy -top pmtest_xilinx_srl_pm_variable
+flatten; opt_clean
+
+design -save gold
+xilinx_srl -variable
+techmap -autoproc -map %model
+design -stash gate
+
+design -copy-from gold -as gold pmtest_xilinx_srl_pm_variable
+design -copy-from gate -as gate pmtest_xilinx_srl_pm_variable
+dff2dffe -unmap # sat does not support flops-with-enable yet
+miter -equiv -flatten -make_assert gold gate miter
+sat -set-init-zero -seq 5 -verify -prove-asserts miter
diff --git a/tests/xilinx/run-test.sh b/tests/xilinx/run-test.sh
new file mode 100755
index 000000000..ea56b70f0
--- /dev/null
+++ b/tests/xilinx/run-test.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+set -e
+{
+echo "all::"
+for x in *.ys; do
+	echo "all:: run-$x"
+	echo "run-$x:"
+	echo "	@echo 'Running $x..'"
+	echo "	@../../yosys -ql ${x%.ys}.log $x"
+done
+for s in *.sh; do
+	if [ "$s" != "run-test.sh" ]; then
+		echo "all:: run-$s"
+		echo "run-$s:"
+		echo "	@echo 'Running $s..'"
+		echo "	@bash $s"
+	fi
+done
+} > run-test.mk
+exec ${MAKE:-make} -f run-test.mk
diff --git a/tests/xilinx/xilinx_srl.v b/tests/xilinx/xilinx_srl.v
new file mode 100644
index 000000000..bc2a15ab2
--- /dev/null
+++ b/tests/xilinx/xilinx_srl.v
@@ -0,0 +1,40 @@
+module xilinx_srl_static_test(input i, clk, output [1:0] q);
+reg head = 1'b0;
+reg [3:0] shift1 = 4'b0000;
+reg [3:0] shift2 = 4'b0000;
+
+always @(posedge clk) begin
+    head <= i;
+    shift1 <= {shift1[2:0], head};
+    shift2 <= {shift2[2:0], head};
+end
+
+assign q = {shift2[3], shift1[3]};
+endmodule
+
+module xilinx_srl_variable_test(input i, clk, input [1:0] l1, l2, output [1:0] q);
+reg head = 1'b0;
+reg [3:0] shift1 = 4'b0000;
+reg [3:0] shift2 = 4'b0000;
+
+always @(posedge clk) begin
+    head <= i;
+    shift1 <= {shift1[2:0], head};
+    shift2 <= {shift2[2:0], head};
+end
+
+assign q = {shift2[l2], shift1[l1]};
+endmodule
+
+module $__XILINX_SHREG_(input C, D, E, input [1:0] L, output Q);
+parameter CLKPOL = 1;
+parameter ENPOL = 1;
+parameter DEPTH = 1;
+parameter [DEPTH-1:0] INIT = {DEPTH{1'b0}};
+reg [DEPTH-1:0] r = INIT;
+wire clk = C ^ CLKPOL;
+always @(posedge C)
+    if (E) 
+        r <= { r[DEPTH-2:0], D };
+assign Q = r[L];
+endmodule
diff --git a/tests/xilinx/xilinx_srl.ys b/tests/xilinx/xilinx_srl.ys
new file mode 100644
index 000000000..b8df0e55a
--- /dev/null
+++ b/tests/xilinx/xilinx_srl.ys
@@ -0,0 +1,67 @@
+read_verilog xilinx_srl.v
+design -save read
+
+design -copy-to model $__XILINX_SHREG_
+hierarchy -top xilinx_srl_static_test
+prep
+design -save gold
+
+techmap
+xilinx_srl -fixed
+opt
+
+# stat
+# show -width
+select -assert-count 1 t:$_DFF_P_
+select -assert-count 2 t:$__XILINX_SHREG_
+
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_
+prep
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+dump gate
+sat -verify -prove-asserts -show-ports -seq 5 miter
+
+#design -load gold
+#stat
+
+#design -load gate
+#stat
+
+##########
+
+design -load read
+design -copy-to model $__XILINX_SHREG_
+hierarchy -top xilinx_srl_variable_test
+prep
+design -save gold
+
+xilinx_srl -variable
+opt
+
+#stat
+# show -width
+# write_verilog -noexpr -norename
+select -assert-count 1 t:$dff
+select -assert-count 1 t:$dff r:WIDTH=1 %i
+select -assert-count 2 t:$__XILINX_SHREG_
+
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_
+prep
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+sat -verify -prove-asserts -show-ports -seq 5 miter
+
+# design -load gold
+# stat
+
+# design -load gate
+# stat