aboutsummaryrefslogtreecommitdiffstats
path: root/passes
diff options
context:
space:
mode:
Diffstat (limited to 'passes')
-rw-r--r--passes/cmds/Makefile.inc1
-rw-r--r--passes/cmds/add.cc18
-rw-r--r--passes/cmds/portlist.cc93
-rw-r--r--passes/cmds/show.cc8
-rw-r--r--passes/equiv/equiv_opt.cc17
-rw-r--r--passes/hierarchy/hierarchy.cc24
-rw-r--r--passes/opt/opt_expr.cc4
-rw-r--r--passes/opt/opt_share.cc7
-rw-r--r--passes/pmgen/.gitignore2
-rw-r--r--passes/pmgen/Makefile.inc9
-rw-r--r--passes/pmgen/README.md2
-rw-r--r--passes/pmgen/ice40_dsp.cc282
-rw-r--r--passes/pmgen/ice40_dsp.pmg625
-rw-r--r--passes/pmgen/peepopt.cc1
-rw-r--r--passes/pmgen/peepopt_dffmux.pmg113
-rw-r--r--passes/pmgen/pmgen.py15
-rw-r--r--passes/pmgen/xilinx_dsp.cc637
-rw-r--r--passes/pmgen/xilinx_dsp.pmg587
-rw-r--r--passes/pmgen/xilinx_dsp_CREG.pmg181
-rw-r--r--passes/pmgen/xilinx_dsp_cascade.pmg336
-rw-r--r--passes/pmgen/xilinx_srl.pmg30
-rw-r--r--passes/sat/async2sync.cc1
-rw-r--r--passes/techmap/Makefile.inc1
-rw-r--r--passes/techmap/abc9.cc79
-rw-r--r--passes/techmap/alumacc.cc19
-rw-r--r--passes/techmap/dff2dffs.cc29
-rw-r--r--passes/techmap/extractinv.cc123
-rw-r--r--passes/techmap/techmap.cc179
-rw-r--r--passes/tests/test_autotb.cc8
29 files changed, 3132 insertions, 299 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc
index c8067a8be..cf9663d1d 100644
--- a/passes/cmds/Makefile.inc
+++ b/passes/cmds/Makefile.inc
@@ -25,6 +25,7 @@ OBJS += passes/cmds/plugin.o
OBJS += passes/cmds/check.o
OBJS += passes/cmds/qwp.o
OBJS += passes/cmds/edgetypes.o
+OBJS += passes/cmds/portlist.o
OBJS += passes/cmds/chformal.o
OBJS += passes/cmds/chtype.o
OBJS += passes/cmds/blackbox.o
diff --git a/passes/cmds/add.cc b/passes/cmds/add.cc
index af6f7043d..dd05ac81f 100644
--- a/passes/cmds/add.cc
+++ b/passes/cmds/add.cc
@@ -105,6 +105,11 @@ struct AddPass : public Pass {
log("Like 'add -input', but also connect the signal between instances of the\n");
log("selected modules.\n");
log("\n");
+ log("\n");
+ log(" add -mod <name[s]>\n");
+ log("\n");
+ log("Add module[s] with the specified name[s].\n");
+ log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
@@ -113,6 +118,7 @@ struct AddPass : public Pass {
bool arg_flag_input = false;
bool arg_flag_output = false;
bool arg_flag_global = false;
+ bool mod_mode = false;
int arg_width = 0;
size_t argidx;
@@ -133,8 +139,20 @@ struct AddPass : public Pass {
arg_width = atoi(args[++argidx].c_str());
continue;
}
+ if (arg == "-mod") {
+ mod_mode = true;
+ argidx++;
+ break;
+ }
break;
}
+
+ if (mod_mode) {
+ for (; argidx < args.size(); argidx++)
+ design->addModule(RTLIL::escape_id(args[argidx]));
+ return;
+ }
+
extra_args(args, argidx, design);
for (auto &mod : design->modules_)
diff --git a/passes/cmds/portlist.cc b/passes/cmds/portlist.cc
new file mode 100644
index 000000000..38c4a8597
--- /dev/null
+++ b/passes/cmds/portlist.cc
@@ -0,0 +1,93 @@
+/*
+ * 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 PortlistPass : public Pass {
+ PortlistPass() : Pass("portlist", "list (top-level) ports") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" portlist [options] [selection]\n");
+ log("\n");
+ log("This command lists all module ports found in the selected modules.\n");
+ log("\n");
+ log("If no selection is provided then it lists the ports on the top module.\n");
+ log("\n");
+ log(" -m\n");
+ log(" print verilog blackbox module definitions instead of port lists\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ bool m_mode = false;
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-m") {
+ m_mode = true;
+ continue;
+ }
+ break;
+ }
+
+ bool first_module = true;
+
+ auto handle_module = [&](RTLIL::Module *module) {
+ vector<string> ports;
+ if (first_module)
+ first_module = false;
+ else
+ log("\n");
+ for (auto port : module->ports) {
+ auto *w = module->wire(port);
+ ports.push_back(stringf("%s [%d:%d] %s", w->port_input ? w->port_output ? "inout" : "input" : "output",
+ w->upto ? w->start_offset : w->start_offset + w->width - 1,
+ w->upto ? w->start_offset + w->width - 1 : w->start_offset,
+ log_id(w)));
+ }
+ log("module %s%s\n", log_id(module), m_mode ? " (" : "");
+ for (int i = 0; i < GetSize(ports); i++)
+ log("%s%s\n", ports[i].c_str(), m_mode && i+1 < GetSize(ports) ? "," : "");
+ if (m_mode)
+ log(");\nendmodule\n");
+ };
+
+ if (argidx == args.size())
+ {
+ auto *top = design->top_module();
+ if (top == nullptr)
+ log_cmd_error("Can't find top module in current design!\n");
+ handle_module(top);
+ }
+ else
+ {
+ extra_args(args, argidx, design);
+ for (auto module : design->selected_modules())
+ handle_module(module);
+ }
+ }
+} PortlistPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc
index 2e9fc72af..a3e969ef1 100644
--- a/passes/cmds/show.cc
+++ b/passes/cmds/show.cc
@@ -26,6 +26,10 @@
# include <dirent.h>
#endif
+#ifdef __APPLE__
+# include <unistd.h>
+#endif
+
#ifdef YOSYS_ENABLE_READLINE
# include <readline/readline.h>
#endif
@@ -866,7 +870,11 @@ struct ShowPass : public Pass {
log_cmd_error("Shell command failed!\n");
} else
if (format.empty()) {
+ #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());
+ #endif
log("Exec: %s\n", cmd.c_str());
if (run_command(cmd) != 0)
log_cmd_error("Shell command failed!\n");
diff --git a/passes/equiv/equiv_opt.cc b/passes/equiv/equiv_opt.cc
index 19d1c25ac..4ab5b1a3e 100644
--- a/passes/equiv/equiv_opt.cc
+++ b/passes/equiv/equiv_opt.cc
@@ -32,7 +32,8 @@ struct EquivOptPass:public ScriptPass
log("\n");
log(" equiv_opt [options] [command]\n");
log("\n");
- log("This command checks circuit equivalence before and after an optimization pass.\n");
+ log("This command uses temporal induction to check circuit equivalence before and\n");
+ log("after an optimization pass.\n");
log("\n");
log(" -run <from_label>:<to_label>\n");
log(" only run the commands between the labels (see below). an empty\n");
@@ -46,6 +47,9 @@ struct EquivOptPass:public ScriptPass
log(" -assert\n");
log(" produce an error if the circuits are not equivalent.\n");
log("\n");
+ log(" -multiclock\n");
+ log(" run clk2fflogic before equivalence checking.\n");
+ log("\n");
log(" -undef\n");
log(" enable modelling of undef states during equiv_induct.\n");
log("\n");
@@ -55,7 +59,7 @@ struct EquivOptPass:public ScriptPass
}
std::string command, techmap_opts;
- bool assert, undef;
+ bool assert, undef, multiclock;
void clear_flags() YS_OVERRIDE
{
@@ -63,6 +67,7 @@ struct EquivOptPass:public ScriptPass
techmap_opts = "";
assert = false;
undef = false;
+ multiclock = false;
}
void execute(std::vector < std::string > args, RTLIL::Design * design) YS_OVERRIDE
@@ -92,6 +97,10 @@ struct EquivOptPass:public ScriptPass
undef = true;
continue;
}
+ if (args[argidx] == "-multiclock") {
+ multiclock = true;
+ continue;
+ }
break;
}
@@ -146,6 +155,10 @@ struct EquivOptPass:public ScriptPass
}
if (check_label("prove")) {
+ if (multiclock || help_mode)
+ run("clk2fflogic", "(only with -multiclock)");
+ if (!multiclock || help_mode)
+ run("async2sync", "(only without -multiclock)");
run("equiv_make gold gate equiv");
if (help_mode)
run("equiv_induct [-undef] equiv");
diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc
index fd95b94b2..d8a628448 100644
--- a/passes/hierarchy/hierarchy.cc
+++ b/passes/hierarchy/hierarchy.cc
@@ -808,6 +808,30 @@ struct HierarchyPass : public Pass {
if (mod_it.second->get_bool_attribute("\\top"))
top_mod = mod_it.second;
+ if (top_mod != nullptr && top_mod->name.begins_with("$abstract")) {
+ IdString top_name = top_mod->name.substr(strlen("$abstract"));
+
+ dict<RTLIL::IdString, RTLIL::Const> top_parameters;
+ for (auto &para : parameters) {
+ SigSpec sig_value;
+ if (!RTLIL::SigSpec::parse(sig_value, NULL, para.second))
+ log_cmd_error("Can't decode value '%s'!\n", para.second.c_str());
+ top_parameters[RTLIL::escape_id(para.first)] = sig_value.as_const();
+ }
+
+ top_mod = design->module(top_mod->derive(design, top_parameters));
+
+ if (top_mod != nullptr && top_mod->name != top_name) {
+ Module *m = top_mod->clone();
+ m->name = top_name;
+ Module *old_mod = design->module(top_name);
+ if (old_mod)
+ design->remove(old_mod);
+ design->add(m);
+ top_mod = m;
+ }
+ }
+
if (top_mod == nullptr && auto_top_mode) {
log_header(design, "Finding top of design hierarchy..\n");
dict<Module*, int> db;
diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc
index 00d7d6063..6cf66fb95 100644
--- a/passes/opt/opt_expr.cc
+++ b/passes/opt/opt_expr.cc
@@ -953,6 +953,10 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
}
if (b.is_fully_const()) {
+ if (b.is_fully_undef()) {
+ RTLIL::SigSpec input = b;
+ ACTION_DO(ID::Y, Const(State::Sx, GetSize(cell->getPort(ID::Y))));
+ } else
if (b.as_bool() == (cell->type == ID($eq))) {
RTLIL::SigSpec input = b;
ACTION_DO(ID::Y, cell->getPort(ID::A));
diff --git a/passes/opt/opt_share.cc b/passes/opt/opt_share.cc
index c53fb3113..2c456705c 100644
--- a/passes/opt/opt_share.cc
+++ b/passes/opt/opt_share.cc
@@ -108,12 +108,13 @@ bool cell_supported(RTLIL::Cell *cell)
return false;
}
-std::map<IdString, IdString> mergeable_type_map{
- {ID($sub), ID($add)},
-};
+std::map<IdString, IdString> mergeable_type_map;
bool mergeable(RTLIL::Cell *a, RTLIL::Cell *b)
{
+ if (mergeable_type_map.empty()) {
+ mergeable_type_map.insert({ID($sub), ID($add)});
+ }
auto a_type = a->type;
if (mergeable_type_map.count(a_type))
a_type = mergeable_type_map.at(a_type);
diff --git a/passes/pmgen/.gitignore b/passes/pmgen/.gitignore
index 6b319b8c3..e52f3282f 100644
--- a/passes/pmgen/.gitignore
+++ b/passes/pmgen/.gitignore
@@ -1 +1 @@
-/*_pm.h \ No newline at end of file
+/*_pm.h
diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc
index 4989c582a..366c37943 100644
--- a/passes/pmgen/Makefile.inc
+++ b/passes/pmgen/Makefile.inc
@@ -21,12 +21,21 @@ $(eval $(call add_extra_objs,passes/pmgen/ice40_wrapcarry_pm.h))
# --------------------------------------
+OBJS += passes/pmgen/xilinx_dsp.o
+passes/pmgen/xilinx_dsp.o: passes/pmgen/xilinx_dsp_pm.h passes/pmgen/xilinx_dsp_CREG_pm.h passes/pmgen/xilinx_dsp_cascade_pm.h
+$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h))
+$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_CREG_pm.h))
+$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_cascade_pm.h))
+
+# --------------------------------------
+
OBJS += passes/pmgen/peepopt.o
passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h
$(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h))
PEEPOPT_PATTERN = passes/pmgen/peepopt_shiftmul.pmg
PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg
+PEEPOPT_PATTERN += passes/pmgen/peepopt_dffmux.pmg
passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
$(P) mkdir -p passes/pmgen && python3 $< -o $@ -p peepopt $(filter-out $<,$^)
diff --git a/passes/pmgen/README.md b/passes/pmgen/README.md
index 0856c9ba3..2f5b8d0b2 100644
--- a/passes/pmgen/README.md
+++ b/passes/pmgen/README.md
@@ -352,7 +352,7 @@ state variables used to pass arguments.
subpattern tail
...
-Subpatterns cann be called recursively.
+Subpatterns can be called recursively.
If a `subpattern` statement is preceded by a `fallthrough` statement, this is
equivalent to calling the subpattern at the end of the preceding block.
diff --git a/passes/pmgen/ice40_dsp.cc b/passes/pmgen/ice40_dsp.cc
index 16bfe537f..f60e67158 100644
--- a/passes/pmgen/ice40_dsp.cc
+++ b/passes/pmgen/ice40_dsp.cc
@@ -29,19 +29,19 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
{
auto &st = pm.st_ice40_dsp;
-#if 0
- log("\n");
- log("ffA: %s\n", log_id(st.ffA, "--"));
- log("ffB: %s\n", log_id(st.ffB, "--"));
- log("mul: %s\n", log_id(st.mul, "--"));
- log("ffY: %s\n", log_id(st.ffY, "--"));
- log("addAB: %s\n", log_id(st.addAB, "--"));
- log("muxAB: %s\n", log_id(st.muxAB, "--"));
- log("ffS: %s\n", log_id(st.ffS, "--"));
-#endif
-
log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul));
+ log_debug("ffA: %s %s %s\n", log_id(st.ffA, "--"), log_id(st.ffAholdmux, "--"), log_id(st.ffArstmux, "--"));
+ log_debug("ffB: %s %s %s\n", log_id(st.ffB, "--"), log_id(st.ffBholdmux, "--"), log_id(st.ffBrstmux, "--"));
+ log_debug("ffCD: %s %s\n", log_id(st.ffCD, "--"), log_id(st.ffCDholdmux, "--"));
+ log_debug("mul: %s\n", log_id(st.mul, "--"));
+ log_debug("ffFJKG: %s\n", log_id(st.ffFJKG, "--"));
+ log_debug("ffH: %s\n", log_id(st.ffH, "--"));
+ log_debug("add: %s\n", log_id(st.add, "--"));
+ log_debug("mux: %s\n", log_id(st.mux, "--"));
+ log_debug("ffO: %s %s %s\n", log_id(st.ffO, "--"), log_id(st.ffOholdmux, "--"), log_id(st.ffOrstmux, "--"));
+ log_debug("\n");
+
if (GetSize(st.sigA) > 16) {
log(" input A (%s) is too large (%d > 16).\n", log_signal(st.sigA), GetSize(st.sigA));
return;
@@ -52,59 +52,85 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
return;
}
- if (GetSize(st.sigS) > 32) {
- log(" accumulator (%s) is too large (%d > 32).\n", log_signal(st.sigS), GetSize(st.sigS));
+ if (GetSize(st.sigO) > 33) {
+ log(" adder/accumulator (%s) is too large (%d > 33).\n", log_signal(st.sigO), GetSize(st.sigO));
return;
}
- if (GetSize(st.sigY) > 32) {
- log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigY), GetSize(st.sigY));
+ if (GetSize(st.sigH) > 32) {
+ log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigH), GetSize(st.sigH));
return;
}
- bool mul_signed = st.mul->getParam("\\A_SIGNED").as_bool();
+ Cell *cell = st.mul;
+ if (cell->type == ID($mul)) {
+ log(" replacing %s with SB_MAC16 cell.\n", log_id(st.mul->type));
- log(" replacing $mul with SB_MAC16 cell.\n");
-
- Cell *cell = pm.module->addCell(NEW_ID, "\\SB_MAC16");
- pm.module->swap_names(cell, st.mul);
+ cell = pm.module->addCell(NEW_ID, ID(SB_MAC16));
+ pm.module->swap_names(cell, st.mul);
+ }
+ else log_assert(cell->type == ID(SB_MAC16));
// SB_MAC16 Input Interface
-
SigSpec A = st.sigA;
- A.extend_u0(16, mul_signed);
+ A.extend_u0(16, st.mul->getParam(ID(A_SIGNED)).as_bool());
+ log_assert(GetSize(A) == 16);
SigSpec B = st.sigB;
- B.extend_u0(16, mul_signed);
-
- SigSpec CD;
- if (st.muxA)
- CD = st.muxA->getPort("\\B");
- if (st.muxB)
- CD = st.muxB->getPort("\\A");
- CD.extend_u0(32, mul_signed);
+ B.extend_u0(16, st.mul->getParam(ID(B_SIGNED)).as_bool());
+ log_assert(GetSize(B) == 16);
- cell->setPort("\\A", A);
- cell->setPort("\\B", B);
- cell->setPort("\\C", CD.extract(0, 16));
- cell->setPort("\\D", CD.extract(16, 16));
+ SigSpec CD = st.sigCD;
+ if (CD.empty())
+ CD = RTLIL::Const(0, 32);
+ else
+ log_assert(GetSize(CD) == 32);
- cell->setParam("\\A_REG", st.ffA ? State::S1 : State::S0);
- cell->setParam("\\B_REG", st.ffB ? State::S1 : State::S0);
+ cell->setPort(ID::A, A);
+ cell->setPort(ID::B, B);
+ cell->setPort(ID(C), CD.extract(16, 16));
+ cell->setPort(ID(D), CD.extract(0, 16));
- cell->setPort("\\AHOLD", State::S0);
- cell->setPort("\\BHOLD", State::S0);
- cell->setPort("\\CHOLD", State::S0);
- cell->setPort("\\DHOLD", State::S0);
+ cell->setParam(ID(A_REG), st.ffA ? State::S1 : State::S0);
+ cell->setParam(ID(B_REG), st.ffB ? State::S1 : State::S0);
+ cell->setParam(ID(C_REG), st.ffCD ? State::S1 : State::S0);
+ cell->setParam(ID(D_REG), st.ffCD ? State::S1 : State::S0);
- cell->setPort("\\IRSTTOP", State::S0);
- cell->setPort("\\IRSTBOT", State::S0);
+ SigSpec AHOLD, BHOLD, CDHOLD;
+ if (st.ffAholdmux)
+ AHOLD = st.ffAholdpol ? st.ffAholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffAholdmux->getPort(ID(S)));
+ else
+ AHOLD = State::S0;
+ if (st.ffBholdmux)
+ BHOLD = st.ffBholdpol ? st.ffBholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBholdmux->getPort(ID(S)));
+ else
+ BHOLD = State::S0;
+ if (st.ffCDholdmux)
+ CDHOLD = st.ffCDholdpol ? st.ffCDholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffCDholdmux->getPort(ID(S)));
+ else
+ CDHOLD = State::S0;
+ cell->setPort(ID(AHOLD), AHOLD);
+ cell->setPort(ID(BHOLD), BHOLD);
+ cell->setPort(ID(CHOLD), CDHOLD);
+ cell->setPort(ID(DHOLD), CDHOLD);
+
+ SigSpec IRSTTOP, IRSTBOT;
+ if (st.ffArstmux)
+ IRSTTOP = st.ffArstpol ? st.ffArstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffArstmux->getPort(ID(S)));
+ else
+ IRSTTOP = State::S0;
+ if (st.ffBrstmux)
+ IRSTBOT = st.ffBrstpol ? st.ffBrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBrstmux->getPort(ID(S)));
+ else
+ IRSTBOT = State::S0;
+ cell->setPort(ID(IRSTTOP), IRSTTOP);
+ cell->setPort(ID(IRSTBOT), IRSTBOT);
- if (st.clock_vld)
+ if (st.clock != SigBit())
{
- cell->setPort("\\CLK", st.clock);
- cell->setPort("\\CE", State::S1);
- cell->setParam("\\NEG_TRIGGER", st.clock_pol ? State::S0 : State::S1);
+ cell->setPort(ID(CLK), st.clock);
+ cell->setPort(ID(CE), State::S1);
+ cell->setParam(ID(NEG_TRIGGER), st.clock_pol ? State::S0 : State::S1);
log(" clock: %s (%s)", log_signal(st.clock), st.clock_pol ? "posedge" : "negedge");
@@ -114,91 +140,137 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
if (st.ffB)
log(" ffB:%s", log_id(st.ffB));
- if (st.ffY)
- log(" ffY:%s", log_id(st.ffY));
+ if (st.ffCD)
+ log(" ffCD:%s", log_id(st.ffCD));
+
+ if (st.ffFJKG)
+ log(" ffFJKG:%s", log_id(st.ffFJKG));
+
+ if (st.ffH)
+ log(" ffH:%s", log_id(st.ffH));
- if (st.ffS)
- log(" ffS:%s", log_id(st.ffS));
+ if (st.ffO)
+ log(" ffO:%s", log_id(st.ffO));
log("\n");
}
else
{
- cell->setPort("\\CLK", State::S0);
- cell->setPort("\\CE", State::S0);
- cell->setParam("\\NEG_TRIGGER", State::S0);
+ cell->setPort(ID(CLK), State::S0);
+ cell->setPort(ID(CE), State::S0);
+ cell->setParam(ID(NEG_TRIGGER), State::S0);
}
// SB_MAC16 Cascade Interface
- cell->setPort("\\SIGNEXTIN", State::Sx);
- cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID));
+ cell->setPort(ID(SIGNEXTIN), State::Sx);
+ cell->setPort(ID(SIGNEXTOUT), pm.module->addWire(NEW_ID));
- cell->setPort("\\CI", State::Sx);
- cell->setPort("\\CO", pm.module->addWire(NEW_ID));
+ cell->setPort(ID(CI), State::Sx);
- cell->setPort("\\ACCUMCI", State::Sx);
- cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID));
+ cell->setPort(ID(ACCUMCI), State::Sx);
+ cell->setPort(ID(ACCUMCO), pm.module->addWire(NEW_ID));
// SB_MAC16 Output Interface
- SigSpec O = st.ffS ? st.sigS : st.sigY;
+ SigSpec O = st.sigO;
+ int O_width = GetSize(O);
+ if (O_width == 33) {
+ log_assert(st.add);
+ // If we have a signed multiply-add, then perform sign extension
+ if (st.add->getParam(ID(A_SIGNED)).as_bool() && st.add->getParam(ID(B_SIGNED)).as_bool())
+ pm.module->connect(O[32], O[31]);
+ else
+ cell->setPort(ID(CO), O[32]);
+ O.remove(O_width-1);
+ }
+ else
+ cell->setPort(ID(CO), pm.module->addWire(NEW_ID));
+ log_assert(GetSize(O) <= 32);
if (GetSize(O) < 32)
O.append(pm.module->addWire(NEW_ID, 32-GetSize(O)));
- cell->setPort("\\O", O);
-
- if (st.addAB) {
- log(" accumulator %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type));
- cell->setPort("\\ADDSUBTOP", st.addAB->type == "$add" ? State::S0 : State::S1);
- cell->setPort("\\ADDSUBBOT", st.addAB->type == "$add" ? State::S0 : State::S1);
+ cell->setPort(ID(O), O);
+
+ bool accum = false;
+ if (st.add) {
+ accum = (st.ffO && st.add->getPort(st.addAB == ID::A ? ID::B : ID::A) == st.sigO);
+ if (accum)
+ log(" accumulator %s (%s)\n", log_id(st.add), log_id(st.add->type));
+ else
+ log(" adder %s (%s)\n", log_id(st.add), log_id(st.add->type));
+ cell->setPort(ID(ADDSUBTOP), st.add->type == ID($add) ? State::S0 : State::S1);
+ cell->setPort(ID(ADDSUBBOT), st.add->type == ID($add) ? State::S0 : State::S1);
} else {
- cell->setPort("\\ADDSUBTOP", State::S0);
- cell->setPort("\\ADDSUBBOT", State::S0);
+ cell->setPort(ID(ADDSUBTOP), State::S0);
+ cell->setPort(ID(ADDSUBBOT), State::S0);
}
- cell->setPort("\\ORSTTOP", State::S0);
- cell->setPort("\\ORSTBOT", State::S0);
+ SigSpec OHOLD;
+ if (st.ffOholdmux)
+ OHOLD = st.ffOholdpol ? st.ffOholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOholdmux->getPort(ID(S)));
+ else
+ OHOLD = State::S0;
+ cell->setPort(ID(OHOLDTOP), OHOLD);
+ cell->setPort(ID(OHOLDBOT), OHOLD);
- cell->setPort("\\OHOLDTOP", State::S0);
- cell->setPort("\\OHOLDBOT", State::S0);
+ SigSpec ORST;
+ if (st.ffOrstmux)
+ ORST = st.ffOrstpol ? st.ffOrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOrstmux->getPort(ID(S)));
+ else
+ ORST = State::S0;
+ cell->setPort(ID(ORSTTOP), ORST);
+ cell->setPort(ID(ORSTBOT), ORST);
SigSpec acc_reset = State::S0;
- if (st.muxA)
- acc_reset = st.muxA->getPort("\\S");
- if (st.muxB)
- acc_reset = pm.module->Not(NEW_ID, st.muxB->getPort("\\S"));
-
- cell->setPort("\\OLOADTOP", acc_reset);
- cell->setPort("\\OLOADBOT", acc_reset);
+ if (st.mux) {
+ if (st.muxAB == ID::A)
+ acc_reset = st.mux->getPort(ID(S));
+ else
+ acc_reset = pm.module->Not(NEW_ID, st.mux->getPort(ID(S)));
+ }
+ cell->setPort(ID(OLOADTOP), acc_reset);
+ cell->setPort(ID(OLOADBOT), acc_reset);
// SB_MAC16 Remaining Parameters
- cell->setParam("\\C_REG", State::S0);
- cell->setParam("\\D_REG", State::S0);
+ cell->setParam(ID(TOP_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
+ cell->setParam(ID(BOT_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
+ cell->setParam(ID(PIPELINE_16x16_MULT_REG1), st.ffFJKG ? State::S1 : State::S0);
+ cell->setParam(ID(PIPELINE_16x16_MULT_REG2), st.ffH ? State::S1 : State::S0);
- cell->setParam("\\TOP_8x8_MULT_REG", st.ffY ? State::S1 : State::S0);
- cell->setParam("\\BOT_8x8_MULT_REG", st.ffY ? State::S1 : State::S0);
- cell->setParam("\\PIPELINE_16x16_MULT_REG1", st.ffY ? State::S1 : State::S0);
- cell->setParam("\\PIPELINE_16x16_MULT_REG2", State::S0);
+ cell->setParam(ID(TOPADDSUB_LOWERINPUT), Const(2, 2));
+ cell->setParam(ID(TOPADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
+ cell->setParam(ID(TOPADDSUB_CARRYSELECT), Const(3, 2));
- cell->setParam("\\TOPOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2));
- cell->setParam("\\TOPADDSUB_LOWERINPUT", Const(2, 2));
- cell->setParam("\\TOPADDSUB_UPPERINPUT", State::S0);
- cell->setParam("\\TOPADDSUB_CARRYSELECT", Const(3, 2));
+ cell->setParam(ID(BOTADDSUB_LOWERINPUT), Const(2, 2));
+ cell->setParam(ID(BOTADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
+ cell->setParam(ID(BOTADDSUB_CARRYSELECT), Const(0, 2));
- cell->setParam("\\BOTOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2));
- cell->setParam("\\BOTADDSUB_LOWERINPUT", Const(2, 2));
- cell->setParam("\\BOTADDSUB_UPPERINPUT", State::S0);
- cell->setParam("\\BOTADDSUB_CARRYSELECT", Const(0, 2));
+ cell->setParam(ID(MODE_8x8), State::S0);
+ cell->setParam(ID(A_SIGNED), st.mul->getParam(ID(A_SIGNED)).as_bool());
+ cell->setParam(ID(B_SIGNED), st.mul->getParam(ID(B_SIGNED)).as_bool());
- cell->setParam("\\MODE_8x8", State::S0);
- cell->setParam("\\A_SIGNED", mul_signed ? State::S1 : State::S0);
- cell->setParam("\\B_SIGNED", mul_signed ? State::S1 : State::S0);
+ if (st.ffO) {
+ if (st.o_lo)
+ cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
+ else
+ cell->setParam(ID(TOPOUTPUT_SELECT), Const(1, 2));
- pm.autoremove(st.mul);
- pm.autoremove(st.ffY);
- pm.autoremove(st.ffS);
+ st.ffO->connections_.at(ID(Q)).replace(O, pm.module->addWire(NEW_ID, GetSize(O)));
+ cell->setParam(ID(BOTOUTPUT_SELECT), Const(1, 2));
+ }
+ else {
+ cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
+ cell->setParam(ID(BOTOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
+ }
+
+ if (cell != st.mul)
+ pm.autoremove(st.mul);
+ else
+ pm.blacklist(st.mul);
+ pm.autoremove(st.ffFJKG);
+ pm.autoremove(st.add);
}
struct Ice40DspPass : public Pass {
@@ -209,7 +281,17 @@ struct Ice40DspPass : public Pass {
log("\n");
log(" ice40_dsp [options] [selection]\n");
log("\n");
- log("Map multipliers and multiply-accumulate blocks to iCE40 DSP resources.\n");
+ log("Map multipliers ($mul/SB_MAC16) and multiply-accumulate ($mul/SB_MAC16 + $add)\n");
+ log("cells into iCE40 DSP resources.\n");
+ log("Currently, only the 16x16 multiply mode is supported and not the 2 x 8x8 mode.\n");
+ log("\n");
+ log("Pack input registers (A, B, {C,D}; with optional hold), pipeline registers\n");
+ log("({F,J,K,G}, H), output registers (O -- full 32-bits or lower 16-bits only; with\n");
+ log("optional hold), and post-adder into into the SB_MAC16 resource.\n");
+ log("\n");
+ log("Multiply-accumulate operations using the post-adder with feedback on the {C,D}\n");
+ log("input will be folded into the DSP. In this scenario only, resetting the\n");
+ log("the accumulator to an arbitrary value can be inferred to use the {C,D} input.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg
index 7003092bb..6b6d2b56f 100644
--- a/passes/pmgen/ice40_dsp.pmg
+++ b/passes/pmgen/ice40_dsp.pmg
@@ -1,163 +1,574 @@
pattern ice40_dsp
state <SigBit> clock
-state <bool> clock_pol clock_vld
-state <SigSpec> sigA sigB sigY sigS
-state <Cell*> addAB muxAB
+state <bool> clock_pol cd_signed o_lo
+state <SigSpec> sigA sigB sigCD sigH sigO
+state <Cell*> add mux
+state <IdString> addAB muxAB
+
+state <bool> ffAholdpol ffBholdpol ffCDholdpol ffOholdpol
+state <bool> ffArstpol ffBrstpol ffCDrstpol ffOrstpol
+
+state <Cell*> ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux
+state <Cell*> ffFJKG ffH ffO ffOholdmux ffOrstmux
+
+// subpattern
+state <SigSpec> argQ argD
+state <bool> ffholdpol ffrstpol
+state <int> ffoffset
+udata <SigSpec> dffD dffQ
+udata <SigBit> dffclock
+udata <Cell*> dff dffholdmux dffrstmux
+udata <bool> dffholdpol dffrstpol dffclock_pol
match mul
- select mul->type.in($mul)
+ select mul->type.in($mul, \SB_MAC16)
select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10
- select GetSize(mul->getPort(\Y)) > 10
endmatch
-match ffA
- select ffA->type.in($dff)
- // select nusers(port(ffA, \Q)) == 2
- index <SigSpec> port(ffA, \Q) === port(mul, \A)
- optional
-endmatch
+code sigA sigB sigH
+ auto unextend = [](const SigSpec &sig) {
+ int i;
+ for (i = GetSize(sig)-1; i > 0; i--)
+ if (sig[i] != sig[i-1])
+ break;
+ // Do not remove non-const sign bit
+ if (sig[i].wire)
+ ++i;
+ return sig.extract(0, i);
+ };
+ sigA = unextend(port(mul, \A));
+ sigB = unextend(port(mul, \B));
-code sigA clock clock_pol clock_vld
- sigA = port(mul, \A);
+ SigSpec O;
+ if (mul->type == $mul)
+ O = mul->getPort(\Y);
+ else if (mul->type == \SB_MAC16)
+ O = mul->getPort(\O);
+ else log_abort();
+ if (GetSize(O) <= 10)
+ reject;
- if (ffA) {
- sigA = port(ffA, \D);
+ // Only care about those bits that are used
+ int i;
+ for (i = 0; i < GetSize(O); i++) {
+ if (nusers(O[i]) <= 1)
+ break;
+ sigH.append(O[i]);
+ }
+ log_assert(nusers(O.extract_end(i)) <= 1);
+endcode
- clock = port(ffA, \CLK).as_bit();
- clock_pol = param(ffA, \CLK_POLARITY).as_bool();
- clock_vld = true;
+code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol
+ if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) {
+ argQ = sigA;
+ subpattern(in_dffe);
+ if (dff) {
+ ffA = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ if (dffrstmux) {
+ ffArstmux = dffrstmux;
+ ffArstpol = dffrstpol;
+ }
+ if (dffholdmux) {
+ ffAholdmux = dffholdmux;
+ ffAholdpol = dffholdpol;
+ }
+ sigA = dffD;
+ }
}
endcode
-match ffB
- select ffB->type.in($dff)
- // select nusers(port(ffB, \Q)) == 2
- index <SigSpec> port(ffB, \Q) === port(mul, \B)
- optional
-endmatch
+code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol
+ if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) {
+ argQ = sigB;
+ subpattern(in_dffe);
+ if (dff) {
+ ffB = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ if (dffrstmux) {
+ ffBrstmux = dffrstmux;
+ ffBrstpol = dffrstpol;
+ }
+ if (dffholdmux) {
+ ffBholdmux = dffholdmux;
+ ffBholdpol = dffholdpol;
+ }
+ sigB = dffD;
+ }
+ }
+endcode
-code sigB clock clock_pol clock_vld
- sigB = port(mul, \B);
+code argD ffFJKG sigH clock clock_pol
+ if (nusers(sigH) == 2 &&
+ (mul->type != \SB_MAC16 ||
+ (!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool()))) {
+ argD = sigH;
+ subpattern(out_dffe);
+ if (dff) {
+ // F/J/K/G do not have a CE-like (hold) input
+ if (dffholdmux)
+ goto reject_ffFJKG;
- if (ffB) {
- sigB = port(ffB, \D);
- SigBit c = port(ffB, \CLK).as_bit();
- bool cp = param(ffB, \CLK_POLARITY).as_bool();
+ // Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT)
+ // shared with A and B
+ if ((ffArstmux != NULL) != (dffrstmux != NULL))
+ goto reject_ffFJKG;
+ if ((ffBrstmux != NULL) != (dffrstmux != NULL))
+ goto reject_ffFJKG;
+ if (ffArstmux) {
+ if (port(ffArstmux, \S) != port(dffrstmux, \S))
+ goto reject_ffFJKG;
+ if (ffArstpol != dffrstpol)
+ goto reject_ffFJKG;
+ }
+ if (ffBrstmux) {
+ if (port(ffBrstmux, \S) != port(dffrstmux, \S))
+ goto reject_ffFJKG;
+ if (ffBrstpol != dffrstpol)
+ goto reject_ffFJKG;
+ }
- if (clock_vld && (c != clock || cp != clock_pol))
- reject;
+ ffFJKG = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ sigH = dffQ;
+
+reject_ffFJKG: ;
+ }
+ }
+endcode
+
+code argD ffH sigH sigO clock clock_pol
+ if (ffFJKG && nusers(sigH) == 2 &&
+ (mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) {
+ argD = sigH;
+ subpattern(out_dffe);
+ if (dff) {
+ // H does not have a CE-like (hold) input
+ if (dffholdmux)
+ goto reject_ffH;
+
+ // Reset signal of H (IRSTBOT) shared with B
+ if ((ffBrstmux != NULL) != (dffrstmux != NULL))
+ goto reject_ffH;
+ if (ffBrstmux) {
+ if (port(ffBrstmux, \S) != port(dffrstmux, \S))
+ goto reject_ffH;
+ if (ffBrstpol != dffrstpol)
+ goto reject_ffH;
+ }
- clock = c;
- clock_pol = cp;
- clock_vld = true;
+ ffH = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ sigH = dffQ;
+
+reject_ffH: ;
+ }
}
+
+ sigO = sigH;
endcode
-match ffY
- select ffY->type.in($dff)
- select nusers(port(ffY, \D)) == 2
- index <SigSpec> port(ffY, \D) === port(mul, \Y)
+match add
+ if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3)
+
+ select add->type.in($add)
+ choice <IdString> AB {\A, \B}
+ select nusers(port(add, AB)) == 2
+
+ index <SigBit> port(add, AB)[0] === sigH[0]
+ filter GetSize(port(add, AB)) <= GetSize(sigH)
+ filter port(add, AB) == sigH.extract(0, GetSize(port(add, AB)))
+ filter nusers(sigH.extract_end(GetSize(port(add, AB)))) <= 1
+ set addAB AB
optional
endmatch
-code sigY clock clock_pol clock_vld
- sigY = port(mul, \Y);
+code sigCD sigO cd_signed
+ if (add) {
+ sigCD = port(add, addAB == \A ? \B : \A);
+ cd_signed = param(add, addAB == \A ? \B_SIGNED : \A_SIGNED).as_bool();
- if (ffY) {
- sigY = port(ffY, \Q);
- SigBit c = port(ffY, \CLK).as_bit();
- bool cp = param(ffY, \CLK_POLARITY).as_bool();
+ int natural_mul_width = GetSize(sigA) + GetSize(sigB);
+ int actual_mul_width = GetSize(sigH);
+ int actual_acc_width = GetSize(sigCD);
- if (clock_vld && (c != clock || cp != clock_pol))
+ if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width))
+ reject;
+ // If accumulator, check adder width and signedness
+ if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(add, \A_SIGNED).as_bool()))
reject;
- clock = c;
- clock_pol = cp;
- clock_vld = true;
+ sigO = port(add, \Y);
}
endcode
-match addA
- select addA->type.in($add)
- select nusers(port(addA, \A)) == 2
- index <SigSpec> port(addA, \A) === sigY
+match mux
+ select mux->type == $mux
+ choice <IdString> AB {\A, \B}
+ select nusers(port(mux, AB)) == 2
+ index <SigSpec> port(mux, AB) === sigO
+ set muxAB AB
optional
endmatch
-match addB
- if !addA
- select addB->type.in($add, $sub)
- select nusers(port(addB, \B)) == 2
- index <SigSpec> port(addB, \B) === sigY
- optional
-endmatch
+code sigO
+ if (mux)
+ sigO = port(mux, \Y);
+endcode
+
+code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo
+ if (mul->type != \SB_MAC16 ||
+ // Ensure that register is not already used
+ ((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) &&
+ // Ensure that OLOADTOP/OLOADBOT is unused or zero
+ (port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) {
+
+ dff = nullptr;
+
+ // First try entire sigO
+ if (nusers(sigO) == 2) {
+ argD = sigO;
+ subpattern(out_dffe);
+ }
+
+ // Otherwise try just its least significant 16 bits
+ if (!dff && GetSize(sigO) > 16) {
+ argD = sigO.extract(0, 16);
+ if (nusers(argD) == 2) {
+ subpattern(out_dffe);
+ o_lo = dff;
+ }
+ }
+
+ if (dff) {
+ ffO = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ if (dffrstmux) {
+ ffOrstmux = dffrstmux;
+ ffOrstpol = dffrstpol;
+ }
+ if (dffholdmux) {
+ ffOholdmux = dffholdmux;
+ ffOholdpol = dffholdpol;
+ }
+
+ sigO.replace(sigO.extract(0, GetSize(dffQ)), dffQ);
+ }
-code addAB sigS
- if (addA) {
- addAB = addA;
- sigS = port(addA, \B);
+ // Loading value into output register is not
+ // supported unless using accumulator
+ if (mux) {
+ if (sigCD != sigO)
+ reject;
+ sigCD = port(mux, muxAB == \B ? \A : \B);
+
+ cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool();
+ }
}
- if (addB) {
- addAB = addB;
- sigS = port(addB, \A);
+endcode
+
+code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol
+ if (!sigCD.empty() && sigCD != sigO &&
+ (mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) {
+ argQ = sigCD;
+ subpattern(in_dffe);
+ if (dff) {
+ if (dffholdmux) {
+ ffCDholdmux = dffholdmux;
+ ffCDholdpol = dffholdpol;
+ }
+
+ // Reset signal of C (IRSTTOP) and D (IRSTBOT)
+ // shared with A and B
+ if ((ffArstmux != NULL) != (dffrstmux != NULL))
+ goto reject_ffCD;
+ if ((ffBrstmux != NULL) != (dffrstmux != NULL))
+ goto reject_ffCD;
+ if (ffArstmux) {
+ if (port(ffArstmux, \S) != port(dffrstmux, \S))
+ goto reject_ffCD;
+ if (ffArstpol != dffrstpol)
+ goto reject_ffCD;
+ }
+ if (ffBrstmux) {
+ if (port(ffBrstmux, \S) != port(dffrstmux, \S))
+ goto reject_ffCD;
+ if (ffBrstpol != dffrstpol)
+ goto reject_ffCD;
+ }
+
+ ffCD = dff;
+ clock = dffclock;
+ clock_pol = dffclock_pol;
+ sigCD = dffD;
+
+reject_ffCD: ;
+ }
}
- if (addAB) {
- int natural_mul_width = GetSize(sigA) + GetSize(sigB);
- int actual_mul_width = GetSize(sigY);
- int actual_acc_width = GetSize(sigS);
+endcode
- if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width))
+code sigCD
+ sigCD.extend_u0(32, cd_signed);
+endcode
+
+code
+ accept;
+endcode
+
+// #######################
+
+subpattern in_dffe
+arg argD argQ clock clock_pol
+
+code
+ dff = nullptr;
+ for (auto c : argQ.chunks()) {
+ if (!c.wire)
+ reject;
+ if (c.wire->get_bool_attribute(\keep))
reject;
- if ((actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool()))
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
reject;
}
endcode
-match muxA
- if addAB
- select muxA->type.in($mux)
- select nusers(port(muxA, \A)) == 2
- index <SigSpec> port(muxA, \A) === port(addAB, \Y)
- optional
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \Q)[offset] === argQ[0]
+
+ // Check that the rest of argQ is present
+ filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
+ filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
endmatch
-match muxB
- if addAB
- if !muxA
- select muxB->type.in($mux)
- select nusers(port(muxB, \B)) == 2
- index <SigSpec> port(muxB, \B) === port(addAB, \Y)
- optional
+code argQ argD
+{
+ if (clock != SigBit()) {
+ if (port(ff, \CLK) != clock)
+ reject;
+ if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
+ reject;
+ }
+
+ SigSpec Q = port(ff, \Q);
+ dff = ff;
+ dffclock = port(ff, \CLK);
+ dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
+ dffD = argQ;
+ argD = port(ff, \D);
+ argQ = Q;
+ dffD.replace(argQ, argD);
+ // Only search for ffrstmux if dffD only
+ // has two (ff, ffrstmux) users
+ if (nusers(dffD) > 2)
+ argD = SigSpec();
+}
+endcode
+
+match ffrstmux
+ if false /* TODO: ice40 resets are actually async */
+
+ if !argD.empty()
+ select ffrstmux->type.in($mux)
+ index <SigSpec> port(ffrstmux, \Y) === argD
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ define <bool> pol (BA == \B)
+ set ffrstpol pol
+ semioptional
endmatch
-code muxAB
- muxAB = addAB;
- if (muxA)
- muxAB = muxA;
- if (muxB)
- muxAB = muxB;
+code argD
+ if (ffrstmux) {
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ argD = port(ffrstmux, ffrstpol ? \A : \B);
+ dffD.replace(port(ffrstmux, \Y), argD);
+
+ // Only search for ffholdmux if argQ has at
+ // least 3 users (ff, <upstream>, ffrstmux) and
+ // dffD only has two (ff, ffrstmux)
+ if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
+ argD = SigSpec();
+ }
+ else
+ dffrstmux = nullptr;
endcode
-match ffS
- if muxAB
- select ffS->type.in($dff)
- select nusers(port(ffS, \D)) == 2
- index <SigSpec> port(ffS, \D) === port(muxAB, \Y)
- index <SigSpec> port(ffS, \Q) === sigS
+match ffholdmux
+ if !argD.empty()
+ select ffholdmux->type.in($mux)
+ index <SigSpec> port(ffholdmux, \Y) === argD
+ choice <IdString> BA {\B, \A}
+ index <SigSpec> port(ffholdmux, BA) === argQ
+ define <bool> pol (BA == \B)
+ set ffholdpol pol
+ semioptional
endmatch
-code clock clock_pol clock_vld
- if (ffS) {
- SigBit c = port(ffS, \CLK).as_bit();
- bool cp = param(ffS, \CLK_POLARITY).as_bool();
+code argD
+ if (ffholdmux) {
+ dffholdmux = ffholdmux;
+ dffholdpol = ffholdpol;
+ argD = port(ffholdmux, ffholdpol ? \A : \B);
+ dffD.replace(port(ffholdmux, \Y), argD);
+ }
+ else
+ dffholdmux = nullptr;
+endcode
- if (clock_vld && (c != clock || cp != clock_pol))
+// #######################
+
+subpattern out_dffe
+arg argD argQ clock clock_pol
+
+code
+ dff = nullptr;
+ for (auto c : argD.chunks())
+ if (c.wire->get_bool_attribute(\keep))
reject;
+endcode
- clock = c;
- clock_pol = cp;
- clock_vld = true;
+match ffholdmux
+ select ffholdmux->type.in($mux)
+ // ffholdmux output must have two users: ffholdmux and ff.D
+ select nusers(port(ffholdmux, \Y)) == 2
+
+ choice <IdString> BA {\B, \A}
+ // keep-last-value net must have at least three users: ffholdmux, ff, downstream sink(s)
+ select nusers(port(ffholdmux, BA)) >= 3
+
+ slice offset GetSize(port(ffholdmux, \Y))
+ define <IdString> AB (BA == \B ? \A : \B)
+ index <SigBit> port(ffholdmux, AB)[offset] === argD[0]
+
+ // Check that the rest of argD is present
+ filter GetSize(port(ffholdmux, AB)) >= offset + GetSize(argD)
+ filter port(ffholdmux, AB).extract(offset, GetSize(argD)) == argD
+
+ set ffoffset offset
+ define <bool> pol (BA == \B)
+ set ffholdpol pol
+
+ semioptional
+endmatch
+
+code argD argQ
+ dffholdmux = ffholdmux;
+ if (ffholdmux) {
+ SigSpec AB = port(ffholdmux, ffholdpol ? \A : \B);
+ SigSpec Y = port(ffholdmux, \Y);
+ argQ = argD;
+ argD.replace(AB, Y);
+ argQ.replace(AB, port(ffholdmux, ffholdpol ? \B : \A));
+
+ dffholdmux = ffholdmux;
+ dffholdpol = ffholdpol;
}
- accept;
+endcode
+
+match ffrstmux
+ if false /* TODO: ice40 resets are actually async */
+
+ select ffrstmux->type.in($mux)
+ // ffrstmux output must have two users: ffrstmux and ff.D
+ select nusers(port(ffrstmux, \Y)) == 2
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ slice offset GetSize(port(ffrstmux, \Y))
+ define <IdString> AB (BA == \B ? \A : \B)
+ index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
+
+ // Check that offset is consistent
+ filter !ffholdmux || ffoffset == offset
+ // Check that the rest of argD is present
+ filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
+ filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
+
+ set ffoffset offset
+ define <bool> pol (AB == \A)
+ set ffrstpol pol
+
+ semioptional
+endmatch
+
+code argD argQ
+ dffrstmux = ffrstmux;
+ if (ffrstmux) {
+ SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
+ SigSpec Y = port(ffrstmux, \Y);
+ argD.replace(AB, Y);
+
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ }
+endcode
+
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \D)[offset] === argD[0]
+
+ // Check that offset is consistent
+ filter (!ffholdmux && !ffrstmux) || ffoffset == offset
+ // Check that the rest of argD is present
+ filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
+ filter port(ff, \D).extract(offset, GetSize(argD)) == argD
+ // Check that FF.Q is connected to CE-mux
+ filter !ffholdmux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
+endmatch
+
+code argQ
+ if (ff) {
+ if (clock != SigBit()) {
+ if (port(ff, \CLK) != clock)
+ reject;
+ if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
+ reject;
+ }
+ SigSpec D = port(ff, \D);
+ SigSpec Q = port(ff, \Q);
+ if (!ffholdmux) {
+ argQ = argD;
+ argQ.replace(D, Q);
+ }
+
+ for (auto c : argQ.chunks()) {
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
+ reject;
+ }
+
+ dff = ff;
+ dffQ = argQ;
+ dffclock = port(ff, \CLK);
+ dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
+ }
+ // No enable/reset mux possible without flop
+ else if (dffholdmux || dffrstmux)
+ reject;
endcode
diff --git a/passes/pmgen/peepopt.cc b/passes/pmgen/peepopt.cc
index e7f95cf85..72b02127a 100644
--- a/passes/pmgen/peepopt.cc
+++ b/passes/pmgen/peepopt.cc
@@ -60,6 +60,7 @@ struct PeepoptPass : public Pass {
peepopt_pm pm(module, module->selected_cells());
pm.run_shiftmul();
pm.run_muldiv();
+ pm.run_dffmux();
}
}
}
diff --git a/passes/pmgen/peepopt_dffmux.pmg b/passes/pmgen/peepopt_dffmux.pmg
new file mode 100644
index 000000000..c88a52226
--- /dev/null
+++ b/passes/pmgen/peepopt_dffmux.pmg
@@ -0,0 +1,113 @@
+pattern dffmux
+
+state <IdString> cemuxAB rstmuxBA
+state <SigSpec> sigD
+
+match dff
+ select dff->type == $dff
+ select GetSize(port(dff, \D)) > 1
+endmatch
+
+match rstmux
+ select rstmux->type == $mux
+ select GetSize(port(rstmux, \Y)) > 1
+ index <SigSpec> port(rstmux, \Y) === port(dff, \D)
+ choice <IdString> BA {\B, \A}
+ select port(rstmux, BA).is_fully_const()
+ set rstmuxBA BA
+ optional
+endmatch
+
+code sigD
+ if (rstmux)
+ sigD = port(rstmux, rstmuxBA == \B ? \A : \B);
+ else
+ sigD = port(dff, \D);
+endcode
+
+match cemux
+ select cemux->type == $mux
+ select GetSize(port(cemux, \Y)) > 1
+ index <SigSpec> port(cemux, \Y) === sigD
+ choice <IdString> AB {\A, \B}
+ index <SigSpec> port(cemux, AB) === port(dff, \Q)
+ set cemuxAB AB
+endmatch
+
+code
+ SigSpec D = port(cemux, cemuxAB == \A ? \B : \A);
+ SigSpec Q = port(dff, \Q);
+ Const rst;
+ if (rstmux)
+ rst = port(rstmux, rstmuxBA).as_const();
+ int width = GetSize(D);
+
+ SigSpec &ceA = cemux->connections_.at(\A);
+ SigSpec &ceB = cemux->connections_.at(\B);
+ SigSpec &ceY = cemux->connections_.at(\Y);
+ SigSpec &dffD = dff->connections_.at(\D);
+ SigSpec &dffQ = dff->connections_.at(\Q);
+
+ if (D[width-1] == D[width-2]) {
+ did_something = true;
+
+ SigBit sign = D[width-1];
+ bool is_signed = sign.wire;
+ int i;
+ for (i = width-1; i >= 2; i--) {
+ if (!is_signed) {
+ module->connect(Q[i], sign);
+ if (D[i-1] != sign || (rst.size() && rst[i-1] != rst[width-1]))
+ break;
+ }
+ else {
+ module->connect(Q[i], Q[i-1]);
+ if (D[i-2] != sign || (rst.size() && rst[i-1] != rst[width-1]))
+ break;
+ }
+ }
+
+ ceA.remove(i, width-i);
+ ceB.remove(i, width-i);
+ ceY.remove(i, width-i);
+ cemux->fixup_parameters();
+ dffD.remove(i, width-i);
+ dffQ.remove(i, width-i);
+ dff->fixup_parameters();
+
+ log("dffcemux pattern in %s: dff=%s, cemux=%s; removed top %d bits.\n", log_id(module), log_id(dff), log_id(cemux), width-i);
+ accept;
+ }
+ else {
+ int count = 0;
+ for (int i = width-1; i >= 0; i--) {
+ if (D[i].wire)
+ continue;
+ Wire *w = Q[i].wire;
+ auto it = w->attributes.find(\init);
+ State init;
+ if (it != w->attributes.end())
+ init = it->second[Q[i].offset];
+ else
+ init = State::Sx;
+
+ if (init == State::Sx || init == D[i].data) {
+ count++;
+ module->connect(Q[i], D[i]);
+ ceA.remove(i);
+ ceB.remove(i);
+ ceY.remove(i);
+ dffD.remove(i);
+ dffQ.remove(i);
+ }
+ }
+ if (count > 0) {
+ did_something = true;
+ cemux->fixup_parameters();
+ dff->fixup_parameters();
+ log("dffcemux pattern in %s: dff=%s, cemux=%s; removed %d constant bits.\n", log_id(module), log_id(dff), log_id(cemux), count);
+ }
+
+ accept;
+ }
+endcode
diff --git a/passes/pmgen/pmgen.py b/passes/pmgen/pmgen.py
index 573722d68..39a09991d 100644
--- a/passes/pmgen/pmgen.py
+++ b/passes/pmgen/pmgen.py
@@ -286,7 +286,7 @@ def process_pmgfile(f, filename):
block["gencode"].append(rewrite_cpp(l.rstrip()))
break
- assert False
+ raise RuntimeError("'%s' statement not recognised on line %d" % (a[0], linenr))
if block["optional"]:
assert not block["semioptional"]
@@ -305,7 +305,8 @@ def process_pmgfile(f, filename):
block["states"] = set()
for s in line.split()[1:]:
- assert s in state_types[current_pattern]
+ if s not in state_types[current_pattern]:
+ raise RuntimeError("'%s' not in state_types" % s)
block["states"].add(s)
codetype = "code"
@@ -327,7 +328,7 @@ def process_pmgfile(f, filename):
blocks.append(block)
continue
- assert False
+ raise RuntimeError("'%s' command not recognised" % cmd)
for fn in pmgfiles:
with open(fn, "r") as f:
@@ -452,11 +453,19 @@ with open(outfile, "w") as f:
print(" return sigmap(cell->getPort(portname));", file=f)
print(" }", file=f)
print("", file=f)
+ print(" SigSpec port(Cell *cell, IdString portname, const SigSpec& defval) {", file=f)
+ print(" return sigmap(cell->connections_.at(portname, defval));", file=f)
+ print(" }", file=f)
+ print("", file=f)
print(" Const param(Cell *cell, IdString paramname) {", file=f)
print(" return cell->getParam(paramname);", file=f)
print(" }", file=f)
print("", file=f)
+ print(" Const param(Cell *cell, IdString paramname, const Const& defval) {", file=f)
+ print(" return cell->parameters.at(paramname, defval);", file=f)
+ print(" }", file=f)
+ print("", file=f)
print(" int nusers(const SigSpec &sig) {", file=f)
print(" pool<Cell*> users;", file=f)
diff --git a/passes/pmgen/xilinx_dsp.cc b/passes/pmgen/xilinx_dsp.cc
new file mode 100644
index 000000000..11c7e5ea8
--- /dev/null
+++ b/passes/pmgen/xilinx_dsp.cc
@@ -0,0 +1,637 @@
+/*
+ * 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/yosys.h"
+#include "kernel/sigtools.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+#include "passes/pmgen/xilinx_dsp_pm.h"
+#include "passes/pmgen/xilinx_dsp_CREG_pm.h"
+#include "passes/pmgen/xilinx_dsp_cascade_pm.h"
+
+static Cell* addDsp(Module *module) {
+ Cell *cell = module->addCell(NEW_ID, ID(DSP48E1));
+ cell->setParam(ID(ACASCREG), 0);
+ cell->setParam(ID(ADREG), 0);
+ cell->setParam(ID(A_INPUT), Const("DIRECT"));
+ cell->setParam(ID(ALUMODEREG), 0);
+ cell->setParam(ID(AREG), 0);
+ cell->setParam(ID(BCASCREG), 0);
+ cell->setParam(ID(B_INPUT), Const("DIRECT"));
+ cell->setParam(ID(BREG), 0);
+ cell->setParam(ID(CARRYINREG), 0);
+ cell->setParam(ID(CARRYINSELREG), 0);
+ cell->setParam(ID(CREG), 0);
+ cell->setParam(ID(DREG), 0);
+ cell->setParam(ID(INMODEREG), 0);
+ cell->setParam(ID(MREG), 0);
+ cell->setParam(ID(OPMODEREG), 0);
+ cell->setParam(ID(PREG), 0);
+ cell->setParam(ID(USE_MULT), Const("NONE"));
+ cell->setParam(ID(USE_SIMD), Const("ONE48"));
+ cell->setParam(ID(USE_DPORT), Const("FALSE"));
+
+ cell->setPort(ID(D), Const(0, 25));
+ cell->setPort(ID(INMODE), Const(0, 5));
+ cell->setPort(ID(ALUMODE), Const(0, 4));
+ cell->setPort(ID(OPMODE), Const(0, 7));
+ cell->setPort(ID(CARRYINSEL), Const(0, 3));
+ cell->setPort(ID(ACIN), Const(0, 30));
+ cell->setPort(ID(BCIN), Const(0, 18));
+ cell->setPort(ID(PCIN), Const(0, 48));
+ cell->setPort(ID(CARRYIN), Const(0, 1));
+ return cell;
+}
+
+void xilinx_simd_pack(Module *module, const std::vector<Cell*> &selected_cells)
+{
+ std::deque<Cell*> simd12_add, simd12_sub;
+ std::deque<Cell*> simd24_add, simd24_sub;
+
+ for (auto cell : selected_cells) {
+ if (!cell->type.in(ID($add), ID($sub)))
+ continue;
+ SigSpec Y = cell->getPort(ID(Y));
+ if (!Y.is_chunk())
+ continue;
+ if (!Y.as_chunk().wire->get_strpool_attribute(ID(use_dsp)).count("simd"))
+ continue;
+ if (GetSize(Y) > 25)
+ continue;
+ SigSpec A = cell->getPort(ID(A));
+ SigSpec B = cell->getPort(ID(B));
+ if (GetSize(Y) <= 13) {
+ if (GetSize(A) > 12)
+ continue;
+ if (GetSize(B) > 12)
+ continue;
+ if (cell->type == ID($add))
+ simd12_add.push_back(cell);
+ else if (cell->type == ID($sub))
+ simd12_sub.push_back(cell);
+ }
+ else if (GetSize(Y) <= 25) {
+ if (GetSize(A) > 24)
+ continue;
+ if (GetSize(B) > 24)
+ continue;
+ if (cell->type == ID($add))
+ simd24_add.push_back(cell);
+ else if (cell->type == ID($sub))
+ simd24_sub.push_back(cell);
+ }
+ else
+ log_abort();
+ }
+
+ auto f12 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) {
+ SigSpec A = lane->getPort(ID(A));
+ SigSpec B = lane->getPort(ID(B));
+ SigSpec Y = lane->getPort(ID(Y));
+ A.extend_u0(12, lane->getParam(ID(A_SIGNED)).as_bool());
+ B.extend_u0(12, lane->getParam(ID(B_SIGNED)).as_bool());
+ AB.append(A);
+ C.append(B);
+ if (GetSize(Y) < 13)
+ Y.append(module->addWire(NEW_ID, 13-GetSize(Y)));
+ else
+ log_assert(GetSize(Y) == 13);
+ P.append(Y.extract(0, 12));
+ CARRYOUT.append(Y[12]);
+ };
+ auto g12 = [&f12,module](std::deque<Cell*> &simd12) {
+ while (simd12.size() > 1) {
+ SigSpec AB, C, P, CARRYOUT;
+
+ Cell *lane1 = simd12.front();
+ simd12.pop_front();
+ Cell *lane2 = simd12.front();
+ simd12.pop_front();
+ Cell *lane3 = nullptr;
+ Cell *lane4 = nullptr;
+
+ if (!simd12.empty()) {
+ lane3 = simd12.front();
+ simd12.pop_front();
+ if (!simd12.empty()) {
+ lane4 = simd12.front();
+ simd12.pop_front();
+ }
+ }
+
+ log("Analysing %s.%s for Xilinx DSP SIMD12 packing.\n", log_id(module), log_id(lane1));
+
+ Cell *cell = addDsp(module);
+ cell->setParam(ID(USE_SIMD), Const("FOUR12"));
+ // X = A:B
+ // Y = 0
+ // Z = C
+ cell->setPort(ID(OPMODE), Const::from_string("0110011"));
+
+ log_assert(lane1);
+ log_assert(lane2);
+ f12(AB, C, P, CARRYOUT, lane1);
+ f12(AB, C, P, CARRYOUT, lane2);
+ if (lane3) {
+ f12(AB, C, P, CARRYOUT, lane3);
+ if (lane4)
+ f12(AB, C, P, CARRYOUT, lane4);
+ else {
+ AB.append(Const(0, 12));
+ C.append(Const(0, 12));
+ P.append(module->addWire(NEW_ID, 12));
+ CARRYOUT.append(module->addWire(NEW_ID, 1));
+ }
+ }
+ else {
+ AB.append(Const(0, 24));
+ C.append(Const(0, 24));
+ P.append(module->addWire(NEW_ID, 24));
+ CARRYOUT.append(module->addWire(NEW_ID, 2));
+ }
+ log_assert(GetSize(AB) == 48);
+ log_assert(GetSize(C) == 48);
+ log_assert(GetSize(P) == 48);
+ log_assert(GetSize(CARRYOUT) == 4);
+ cell->setPort(ID(A), AB.extract(18, 30));
+ cell->setPort(ID(B), AB.extract(0, 18));
+ cell->setPort(ID(C), C);
+ cell->setPort(ID(P), P);
+ cell->setPort(ID(CARRYOUT), CARRYOUT);
+ if (lane1->type == ID($sub))
+ cell->setPort(ID(ALUMODE), Const::from_string("0011"));
+
+ module->remove(lane1);
+ module->remove(lane2);
+ if (lane3) module->remove(lane3);
+ if (lane4) module->remove(lane4);
+
+ module->design->select(module, cell);
+ }
+ };
+ g12(simd12_add);
+ g12(simd12_sub);
+
+ auto f24 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) {
+ SigSpec A = lane->getPort(ID(A));
+ SigSpec B = lane->getPort(ID(B));
+ SigSpec Y = lane->getPort(ID(Y));
+ A.extend_u0(24, lane->getParam(ID(A_SIGNED)).as_bool());
+ B.extend_u0(24, lane->getParam(ID(B_SIGNED)).as_bool());
+ C.append(A);
+ AB.append(B);
+ if (GetSize(Y) < 25)
+ Y.append(module->addWire(NEW_ID, 25-GetSize(Y)));
+ else
+ log_assert(GetSize(Y) == 25);
+ P.append(Y.extract(0, 24));
+ CARRYOUT.append(module->addWire(NEW_ID)); // TWO24 uses every other bit
+ CARRYOUT.append(Y[24]);
+ };
+ auto g24 = [&f24,module](std::deque<Cell*> &simd24) {
+ while (simd24.size() > 1) {
+ SigSpec AB;
+ SigSpec C;
+ SigSpec P;
+ SigSpec CARRYOUT;
+
+ Cell *lane1 = simd24.front();
+ simd24.pop_front();
+ Cell *lane2 = simd24.front();
+ simd24.pop_front();
+
+ log("Analysing %s.%s for Xilinx DSP SIMD24 packing.\n", log_id(module), log_id(lane1));
+
+ Cell *cell = addDsp(module);
+ cell->setParam(ID(USE_SIMD), Const("TWO24"));
+ // X = A:B
+ // Y = 0
+ // Z = C
+ cell->setPort(ID(OPMODE), Const::from_string("0110011"));
+
+ log_assert(lane1);
+ log_assert(lane2);
+ f24(AB, C, P, CARRYOUT, lane1);
+ f24(AB, C, P, CARRYOUT, lane2);
+ log_assert(GetSize(AB) == 48);
+ log_assert(GetSize(C) == 48);
+ log_assert(GetSize(P) == 48);
+ log_assert(GetSize(CARRYOUT) == 4);
+ cell->setPort(ID(A), AB.extract(18, 30));
+ cell->setPort(ID(B), AB.extract(0, 18));
+ cell->setPort(ID(C), C);
+ cell->setPort(ID(P), P);
+ cell->setPort(ID(CARRYOUT), CARRYOUT);
+ if (lane1->type == ID($sub))
+ cell->setPort(ID(ALUMODE), Const::from_string("0011"));
+
+ module->remove(lane1);
+ module->remove(lane2);
+
+ module->design->select(module, cell);
+ }
+ };
+ g24(simd24_add);
+ g24(simd24_sub);
+}
+
+void xilinx_dsp_pack(xilinx_dsp_pm &pm)
+{
+ auto &st = pm.st_xilinx_dsp_pack;
+
+ log("Analysing %s.%s for Xilinx DSP packing.\n", log_id(pm.module), log_id(st.dsp));
+
+ log_debug("preAdd: %s\n", log_id(st.preAdd, "--"));
+ log_debug("ffAD: %s %s %s\n", log_id(st.ffAD, "--"), log_id(st.ffADcemux, "--"), log_id(st.ffADrstmux, "--"));
+ log_debug("ffA2: %s %s %s\n", log_id(st.ffA2, "--"), log_id(st.ffA2cemux, "--"), log_id(st.ffA2rstmux, "--"));
+ log_debug("ffA1: %s %s %s\n", log_id(st.ffA1, "--"), log_id(st.ffA1cemux, "--"), log_id(st.ffA1rstmux, "--"));
+ log_debug("ffB2: %s %s %s\n", log_id(st.ffB2, "--"), log_id(st.ffB2cemux, "--"), log_id(st.ffB2rstmux, "--"));
+ log_debug("ffB1: %s %s %s\n", log_id(st.ffB1, "--"), log_id(st.ffB1cemux, "--"), log_id(st.ffB1rstmux, "--"));
+ log_debug("ffD: %s %s %s\n", log_id(st.ffD, "--"), log_id(st.ffDcemux, "--"), log_id(st.ffDrstmux, "--"));
+ log_debug("dsp: %s\n", log_id(st.dsp, "--"));
+ log_debug("ffM: %s %s %s\n", log_id(st.ffM, "--"), log_id(st.ffMcemux, "--"), log_id(st.ffMrstmux, "--"));
+ log_debug("postAdd: %s\n", log_id(st.postAdd, "--"));
+ log_debug("postAddMux: %s\n", log_id(st.postAddMux, "--"));
+ log_debug("ffP: %s %s %s\n", log_id(st.ffP, "--"), log_id(st.ffPcemux, "--"), log_id(st.ffPrstmux, "--"));
+ log_debug("overflow: %s\n", log_id(st.overflow, "--"));
+
+ Cell *cell = st.dsp;
+
+ if (st.preAdd) {
+ log(" preadder %s (%s)\n", log_id(st.preAdd), log_id(st.preAdd->type));
+ bool A_SIGNED = st.preAdd->getParam(ID(A_SIGNED)).as_bool();
+ bool D_SIGNED = st.preAdd->getParam(ID(B_SIGNED)).as_bool();
+ if (st.sigA == st.preAdd->getPort(ID(B)))
+ std::swap(A_SIGNED, D_SIGNED);
+ st.sigA.extend_u0(30, A_SIGNED);
+ st.sigD.extend_u0(25, D_SIGNED);
+ cell->setPort(ID(A), st.sigA);
+ cell->setPort(ID(D), st.sigD);
+ cell->setPort(ID(INMODE), Const::from_string("00100"));
+
+ if (st.ffAD) {
+ if (st.ffADcemux) {
+ SigSpec S = st.ffADcemux->getPort(ID(S));
+ cell->setPort(ID(CEAD), st.ffADcepol ? S : pm.module->Not(NEW_ID, S));
+ }
+ else
+ cell->setPort(ID(CEAD), State::S1);
+ cell->setParam(ID(ADREG), 1);
+ }
+
+ cell->setParam(ID(USE_DPORT), Const("TRUE"));
+
+ pm.autoremove(st.preAdd);
+ }
+ if (st.postAdd) {
+ log(" postadder %s (%s)\n", log_id(st.postAdd), log_id(st.postAdd->type));
+
+ SigSpec &opmode = cell->connections_.at(ID(OPMODE));
+ if (st.postAddMux) {
+ log_assert(st.ffP);
+ opmode[4] = st.postAddMux->getPort(ID(S));
+ pm.autoremove(st.postAddMux);
+ }
+ else if (st.ffP && st.sigC == st.sigP)
+ opmode[4] = State::S0;
+ else
+ opmode[4] = State::S1;
+ opmode[6] = State::S0;
+ opmode[5] = State::S1;
+
+ if (opmode[4] != State::S0) {
+ if (st.postAddMuxAB == ID(A))
+ st.sigC.extend_u0(48, st.postAdd->getParam(ID(B_SIGNED)).as_bool());
+ else
+ st.sigC.extend_u0(48, st.postAdd->getParam(ID(A_SIGNED)).as_bool());
+ cell->setPort(ID(C), st.sigC);
+ }
+
+ pm.autoremove(st.postAdd);
+ }
+ if (st.overflow) {
+ log(" overflow %s (%s)\n", log_id(st.overflow), log_id(st.overflow->type));
+ cell->setParam(ID(USE_PATTERN_DETECT), Const("PATDET"));
+ cell->setParam(ID(SEL_PATTERN), Const("PATTERN"));
+ cell->setParam(ID(SEL_MASK), Const("MASK"));
+
+ if (st.overflow->type == ID($ge)) {
+ Const B = st.overflow->getPort(ID(B)).as_const();
+ log_assert(std::count(B.bits.begin(), B.bits.end(), State::S1) == 1);
+ // Since B is an exact power of 2, subtract 1
+ // by inverting all bits up until hitting
+ // that one hi bit
+ for (auto &b : B.bits)
+ if (b == State::S0) b = State::S1;
+ else if (b == State::S1) {
+ b = State::S0;
+ break;
+ }
+ B.extu(48);
+
+ cell->setParam(ID(MASK), B);
+ cell->setParam(ID(PATTERN), Const(0, 48));
+ cell->setPort(ID(OVERFLOW), st.overflow->getPort(ID(Y)));
+ }
+ else log_abort();
+
+ pm.autoremove(st.overflow);
+ }
+
+ if (st.clock != SigBit())
+ {
+ cell->setPort(ID(CLK), st.clock);
+
+ auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
+ SigSpec D = ff->getPort(ID(D));
+ SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
+ if (!A.empty())
+ A.replace(Q, D);
+ if (rstmux) {
+ SigSpec Y = rstmux->getPort(ID(Y));
+ SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
+ if (!A.empty())
+ A.replace(Y, AB);
+ if (rstport != IdString()) {
+ SigSpec S = rstmux->getPort(ID(S));
+ cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
+ }
+ }
+ else if (rstport != IdString())
+ cell->setPort(rstport, State::S0);
+ if (cemux) {
+ SigSpec Y = cemux->getPort(ID(Y));
+ SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
+ SigSpec S = cemux->getPort(ID(S));
+ if (!A.empty())
+ A.replace(Y, BA);
+ cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
+ }
+ else
+ cell->setPort(ceport, State::S1);
+
+ for (auto c : Q.chunks()) {
+ auto it = c.wire->attributes.find(ID(init));
+ if (it == c.wire->attributes.end())
+ continue;
+ for (int i = c.offset; i < c.offset+c.width; i++) {
+ log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx);
+ it->second[i] = State::Sx;
+ }
+ }
+ };
+
+ if (st.ffA2) {
+ SigSpec A = cell->getPort(ID(A));
+ f(A, st.ffA2, st.ffA2cemux, st.ffA2cepol, ID(CEA2), st.ffA2rstmux, st.ffArstpol, ID(RSTA));
+ if (st.ffA1) {
+ f(A, st.ffA1, st.ffA1cemux, st.ffA1cepol, ID(CEA1), st.ffA1rstmux, st.ffArstpol, IdString());
+ cell->setParam(ID(AREG), 2);
+ cell->setParam(ID(ACASCREG), 2);
+ }
+ else {
+ cell->setParam(ID(AREG), 1);
+ cell->setParam(ID(ACASCREG), 1);
+ }
+ pm.add_siguser(A, cell);
+ cell->setPort(ID(A), A);
+ }
+ if (st.ffB2) {
+ SigSpec B = cell->getPort(ID(B));
+ f(B, st.ffB2, st.ffB2cemux, st.ffB2cepol, ID(CEB2), st.ffB2rstmux, st.ffBrstpol, ID(RSTB));
+ if (st.ffB1) {
+ f(B, st.ffB1, st.ffB1cemux, st.ffB1cepol, ID(CEB1), st.ffB1rstmux, st.ffBrstpol, IdString());
+ cell->setParam(ID(BREG), 2);
+ cell->setParam(ID(BCASCREG), 2);
+ }
+ else {
+ cell->setParam(ID(BREG), 1);
+ cell->setParam(ID(BCASCREG), 1);
+ }
+ pm.add_siguser(B, cell);
+ cell->setPort(ID(B), B);
+ }
+ if (st.ffD) {
+ SigSpec D = cell->getPort(ID(D));
+ f(D, st.ffD, st.ffDcemux, st.ffDcepol, ID(CED), st.ffDrstmux, st.ffDrstpol, ID(RSTD));
+ pm.add_siguser(D, cell);
+ cell->setPort(ID(D), D);
+ cell->setParam(ID(DREG), 1);
+ }
+ if (st.ffM) {
+ SigSpec M; // unused
+ f(M, st.ffM, st.ffMcemux, st.ffMcepol, ID(CEM), st.ffMrstmux, st.ffMrstpol, ID(RSTM));
+ st.ffM->connections_.at(ID(Q)).replace(st.sigM, pm.module->addWire(NEW_ID, GetSize(st.sigM)));
+ cell->setParam(ID(MREG), State::S1);
+ }
+ if (st.ffP) {
+ SigSpec P; // unused
+ f(P, st.ffP, st.ffPcemux, st.ffPcepol, ID(CEP), st.ffPrstmux, st.ffPrstpol, ID(RSTP));
+ st.ffP->connections_.at(ID(Q)).replace(st.sigP, pm.module->addWire(NEW_ID, GetSize(st.sigP)));
+ cell->setParam(ID(PREG), State::S1);
+ }
+
+ log(" clock: %s (%s)", log_signal(st.clock), "posedge");
+
+ if (st.ffA2) {
+ log(" ffA2:%s", log_id(st.ffA2));
+ if (st.ffA1)
+ log(" ffA1:%s", log_id(st.ffA1));
+ }
+
+ if (st.ffAD)
+ log(" ffAD:%s", log_id(st.ffAD));
+
+ if (st.ffB2) {
+ log(" ffB2:%s", log_id(st.ffB2));
+ if (st.ffB1)
+ log(" ffB1:%s", log_id(st.ffB1));
+ }
+
+ if (st.ffD)
+ log(" ffD:%s", log_id(st.ffD));
+
+ if (st.ffM)
+ log(" ffM:%s", log_id(st.ffM));
+
+ if (st.ffP)
+ log(" ffP:%s", log_id(st.ffP));
+ }
+ log("\n");
+
+ SigSpec P = st.sigP;
+ if (GetSize(P) < 48)
+ P.append(pm.module->addWire(NEW_ID, 48-GetSize(P)));
+ cell->setPort(ID(P), P);
+
+ pm.blacklist(cell);
+}
+
+void xilinx_dsp_packC(xilinx_dsp_CREG_pm &pm)
+{
+ auto &st = pm.st_xilinx_dsp_packC;
+
+ log_debug("Analysing %s.%s for Xilinx DSP packing (CREG).\n", log_id(pm.module), log_id(st.dsp));
+ log_debug("ffC: %s %s %s\n", log_id(st.ffC, "--"), log_id(st.ffCcemux, "--"), log_id(st.ffCrstmux, "--"));
+
+ Cell *cell = st.dsp;
+
+ if (st.clock != SigBit())
+ {
+ cell->setPort(ID(CLK), st.clock);
+
+ auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
+ SigSpec D = ff->getPort(ID(D));
+ SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
+ if (!A.empty())
+ A.replace(Q, D);
+ if (rstmux) {
+ SigSpec Y = rstmux->getPort(ID(Y));
+ SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
+ if (!A.empty())
+ A.replace(Y, AB);
+ if (rstport != IdString()) {
+ SigSpec S = rstmux->getPort(ID(S));
+ cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
+ }
+ }
+ else if (rstport != IdString())
+ cell->setPort(rstport, State::S0);
+ if (cemux) {
+ SigSpec Y = cemux->getPort(ID(Y));
+ SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
+ SigSpec S = cemux->getPort(ID(S));
+ if (!A.empty())
+ A.replace(Y, BA);
+ cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
+ }
+ else
+ cell->setPort(ceport, State::S1);
+
+ for (auto c : Q.chunks()) {
+ auto it = c.wire->attributes.find(ID(init));
+ if (it == c.wire->attributes.end())
+ continue;
+ for (int i = c.offset; i < c.offset+c.width; i++) {
+ log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx);
+ it->second[i] = State::Sx;
+ }
+ }
+ };
+
+ if (st.ffC) {
+ SigSpec C = cell->getPort(ID(C));
+ f(C, st.ffC, st.ffCcemux, st.ffCcepol, ID(CEC), st.ffCrstmux, st.ffCrstpol, ID(RSTC));
+ pm.add_siguser(C, cell);
+ cell->setPort(ID(C), C);
+ cell->setParam(ID(CREG), 1);
+ }
+
+ log(" clock: %s (%s)", log_signal(st.clock), "posedge");
+
+ if (st.ffC)
+ log(" ffC:%s", log_id(st.ffC));
+ log("\n");
+ }
+
+ pm.blacklist(cell);
+}
+
+struct XilinxDspPass : public Pass {
+ XilinxDspPass() : Pass("xilinx_dsp", "Xilinx: pack resources into DSPs") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" xilinx_dsp [options] [selection]\n");
+ log("\n");
+ log("Pack input registers (A2, A1, B2, B1, C, D, AD; with optional enable/reset),\n");
+ log("pipeline registers (M; with optional enable/reset), output registers (P; with\n");
+ log("optional enable/reset), pre-adder and/or post-adder into Xilinx DSP resources.\n");
+ log("\n");
+ log("Multiply-accumulate operations using the post-adder with feedback on the 'C'\n");
+ log("input will be folded into the DSP. In this scenario only, the 'C' input can be\n");
+ log("used to override the current accumulation result with a new value, which will\n");
+ log("be added to the multiplier result to form the next accumulation result.\n");
+ log("\n");
+ log("Use of the dedicated 'PCOUT' -> 'PCIN' cascade path is detected for 'P' -> 'C'\n");
+ log("connections (optionally, where 'P' is right-shifted by 17-bits and used as an\n");
+ log("input to the post-adder -- a pattern common for summing partial products to\n");
+ log("implement wide multipliers). Limited support also exists for similar cascading\n");
+ log("for A and B using '[AB]COUT' -> '[AB]CIN'. Currently, cascade chains are limited\n");
+ log("to a maximum length of 20 cells, corresponding to the smallest Xilinx 7 Series\n");
+ log("device.\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");
+ log("the add/subtract operator will cause those operations to be implemented using\n");
+ log("the 'SIMD' feature of DSPs.\n");
+ log("\n");
+ log("Experimental feature: the presence of a `$ge' cell attached to the registered\n");
+ log("P output implementing the operation \"(P >= <power-of-2>)\" will be transformed\n");
+ log("into using the DSP48E1's pattern detector feature for overflow detection.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ log_header(design, "Executing XILINX_DSP pass (pack resources into DSPs).\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++)
+ {
+ // if (args[argidx] == "-singleton") {
+ // singleton_mode = true;
+ // continue;
+ // }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules()) {
+ xilinx_simd_pack(module, module->selected_cells());
+
+ {
+ xilinx_dsp_pm pm(module, module->selected_cells());
+ pm.run_xilinx_dsp_pack(xilinx_dsp_pack);
+ }
+ // Separating out CREG packing is necessary since there
+ // is no guarantee that the cell ordering corresponds
+ // to the "expected" case (i.e. the order in which
+ // they appear in the source) thus the possiblity
+ // existed that a register got packed as CREG into a
+ // downstream DSP that should have otherwise been a
+ // PREG of an upstream DSP that had not been pattern
+ // matched yet
+ {
+ xilinx_dsp_CREG_pm pm(module, module->selected_cells());
+ pm.run_xilinx_dsp_packC(xilinx_dsp_packC);
+ }
+ {
+ xilinx_dsp_cascade_pm pm(module, module->selected_cells());
+ pm.run_xilinx_dsp_cascade();
+ }
+ }
+ }
+} XilinxDspPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/pmgen/xilinx_dsp.pmg b/passes/pmgen/xilinx_dsp.pmg
new file mode 100644
index 000000000..3d0b1f2c3
--- /dev/null
+++ b/passes/pmgen/xilinx_dsp.pmg
@@ -0,0 +1,587 @@
+pattern xilinx_dsp_pack
+
+state <SigBit> clock
+state <SigSpec> sigA sigB sigC sigD sigM sigP
+state <IdString> postAddAB postAddMuxAB
+state <bool> ffA1cepol ffA2cepol ffADcepol ffB1cepol ffB2cepol ffDcepol ffMcepol ffPcepol
+state <bool> ffArstpol ffADrstpol ffBrstpol ffDrstpol ffMrstpol ffPrstpol
+
+state <Cell*> ffAD ffADcemux ffADrstmux ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux
+state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux
+state <Cell*> ffD ffDcemux ffDrstmux ffM ffMcemux ffMrstmux ffP ffPcemux ffPrstmux
+
+// subpattern
+state <SigSpec> argQ argD
+state <bool> ffcepol ffrstpol
+state <int> ffoffset
+udata <SigSpec> dffD dffQ
+udata <SigBit> dffclock
+udata <Cell*> dff dffcemux dffrstmux
+udata <bool> dffcepol dffrstpol
+
+match dsp
+ select dsp->type.in(\DSP48E1)
+endmatch
+
+code sigA sigB sigC sigD sigM clock
+ auto unextend = [](const SigSpec &sig) {
+ int i;
+ for (i = GetSize(sig)-1; i > 0; i--)
+ if (sig[i] != sig[i-1])
+ break;
+ // Do not remove non-const sign bit
+ if (sig[i].wire)
+ ++i;
+ return sig.extract(0, i);
+ };
+ sigA = unextend(port(dsp, \A));
+ sigB = unextend(port(dsp, \B));
+
+ sigC = port(dsp, \C, SigSpec());
+ sigD = port(dsp, \D, SigSpec());
+
+ SigSpec P = port(dsp, \P);
+ if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") {
+ // Only care about those bits that are used
+ int i;
+ for (i = 0; i < GetSize(P); i++) {
+ if (nusers(P[i]) <= 1)
+ break;
+ sigM.append(P[i]);
+ }
+ log_assert(nusers(P.extract_end(i)) <= 1);
+ }
+ else
+ sigM = P;
+ // This sigM could have no users if downstream $add
+ // is narrower than $mul result, for example
+ if (sigM.empty())
+ reject;
+
+ clock = port(dsp, \CLK, SigBit());
+endcode
+
+code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock
+ if (param(dsp, \ADREG).as_int() == 0) {
+ argQ = sigA;
+ subpattern(in_dffe);
+ if (dff) {
+ ffAD = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffADrstmux = dffrstmux;
+ ffADrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffADcemux = dffcemux;
+ ffADcepol = dffcepol;
+ }
+ sigA = dffD;
+ }
+ }
+endcode
+
+match preAdd
+ if sigD.empty() || sigD.is_fully_zero()
+ // Ensure that preAdder not already used
+ if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE"
+ if port(dsp, \INMODE, Const(0, 5)).is_fully_zero()
+
+ select preAdd->type.in($add)
+ // Output has to be 25 bits or less
+ select GetSize(port(preAdd, \Y)) <= 25
+ select nusers(port(preAdd, \Y)) == 2
+ choice <IdString> AB {\A, \B}
+ // A port has to be 30 bits or less
+ select GetSize(port(preAdd, AB)) <= 30
+ define <IdString> BA (AB == \A ? \B : \A)
+ // D port has to be 25 bits or less
+ select GetSize(port(preAdd, BA)) <= 25
+ index <SigSpec> port(preAdd, \Y) === sigA
+
+ optional
+endmatch
+
+code sigA sigD
+ if (preAdd) {
+ sigA = port(preAdd, \A);
+ sigD = port(preAdd, \B);
+ if (GetSize(sigA) < GetSize(sigD))
+ std::swap(sigA, sigD);
+ }
+endcode
+
+code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol
+ // Only search for ffA2 if there was a pre-adder
+ // (otherwise ffA2 would have been matched as ffAD)
+ if (preAdd) {
+ if (param(dsp, \AREG).as_int() == 0) {
+ argQ = sigA;
+ subpattern(in_dffe);
+ if (dff) {
+ ffA2 = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffA2rstmux = dffrstmux;
+ ffArstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffA2cepol = dffcepol;
+ ffA2cemux = dffcemux;
+ }
+ sigA = dffD;
+ }
+ }
+ }
+ // And if there wasn't a pre-adder,
+ // move AD register to A
+ else if (ffAD) {
+ log_assert(!ffA2 && !ffA2cemux && !ffA2rstmux);
+ std::swap(ffA2, ffAD);
+ std::swap(ffA2cemux, ffADcemux);
+ std::swap(ffA2rstmux, ffADrstmux);
+ ffA2cepol = ffADcepol;
+ ffArstpol = ffADrstpol;
+ }
+
+ // Now attempt to match A1
+ if (ffA2) {
+ argQ = sigA;
+ subpattern(in_dffe);
+ if (dff) {
+ if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr))
+ goto ffA1_end;
+ if (dffrstmux) {
+ if (ffArstpol != dffrstpol)
+ goto ffA1_end;
+ if (port(ffA2rstmux, \S) != port(dffrstmux, \S))
+ goto ffA1_end;
+ ffA1rstmux = dffrstmux;
+ }
+
+ ffA1 = dff;
+ clock = dffclock;
+
+ if (dffcemux) {
+ ffA1cemux = dffcemux;
+ ffA1cepol = dffcepol;
+ }
+ sigA = dffD;
+
+ffA1_end: ;
+ }
+ }
+endcode
+
+code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol
+ if (param(dsp, \BREG).as_int() == 0) {
+ argQ = sigB;
+ subpattern(in_dffe);
+ if (dff) {
+ ffB2 = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffB2rstmux = dffrstmux;
+ ffBrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffB2cemux = dffcemux;
+ ffB2cepol = dffcepol;
+ }
+ sigB = dffD;
+
+ // Now attempt to match B1
+ if (ffB2) {
+ argQ = sigB;
+ subpattern(in_dffe);
+ if (dff) {
+ if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr))
+ goto ffB1_end;
+ if (dffrstmux) {
+ if (ffBrstpol != dffrstpol)
+ goto ffB1_end;
+ if (port(ffB2rstmux, \S) != port(dffrstmux, \S))
+ goto ffB1_end;
+ ffB1rstmux = dffrstmux;
+ }
+
+ ffB1 = dff;
+ clock = dffclock;
+
+ if (dffcemux) {
+ ffB1cemux = dffcemux;
+ ffB1cepol = dffcepol;
+ }
+ sigB = dffD;
+
+ffB1_end: ;
+ }
+ }
+
+ }
+ }
+endcode
+
+code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock
+ if (param(dsp, \DREG).as_int() == 0) {
+ argQ = sigD;
+ subpattern(in_dffe);
+ if (dff) {
+ ffD = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffDrstmux = dffrstmux;
+ ffDrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffDcemux = dffcemux;
+ ffDcepol = dffcepol;
+ }
+ sigD = dffD;
+ }
+ }
+endcode
+
+code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol sigM sigP clock
+ if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) {
+ argD = sigM;
+ subpattern(out_dffe);
+ if (dff) {
+ ffM = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffMrstmux = dffrstmux;
+ ffMrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffMcemux = dffcemux;
+ ffMcepol = dffcepol;
+ }
+ sigM = dffQ;
+ }
+ }
+ sigP = sigM;
+endcode
+
+match postAdd
+ // Ensure that Z mux is not already used
+ if port(dsp, \OPMODE, SigSpec()).extract(4,3).is_fully_zero()
+
+ select postAdd->type.in($add)
+ select GetSize(port(postAdd, \Y)) <= 48
+ choice <IdString> AB {\A, \B}
+ select nusers(port(postAdd, AB)) <= 3
+ filter ffMcemux || nusers(port(postAdd, AB)) == 2
+ filter !ffMcemux || nusers(port(postAdd, AB)) == 3
+
+ index <SigBit> port(postAdd, AB)[0] === sigP[0]
+ filter GetSize(port(postAdd, AB)) >= GetSize(sigP)
+ filter port(postAdd, AB).extract(0, GetSize(sigP)) == sigP
+ filter port(postAdd, AB).extract_end(GetSize(sigP)) == SigSpec(sigP[GetSize(sigP)-1], GetSize(port(postAdd, AB))-GetSize(sigP))
+ set postAddAB AB
+ optional
+endmatch
+
+code sigC sigP
+ if (postAdd) {
+ sigC = port(postAdd, postAddAB == \A ? \B : \A);
+ sigP = port(postAdd, \Y);
+ }
+endcode
+
+code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock
+ if (param(dsp, \PREG).as_int() == 0) {
+ int users = 2;
+ // If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux
+ if (ffMcemux && !postAdd) users++;
+ if (nusers(sigP) == users) {
+ argD = sigP;
+ subpattern(out_dffe);
+ if (dff) {
+ ffP = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffPrstmux = dffrstmux;
+ ffPrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffPcemux = dffcemux;
+ ffPcepol = dffcepol;
+ }
+ sigP = dffQ;
+ }
+ }
+ }
+endcode
+
+match postAddMux
+ if postAdd
+ if ffP
+ select postAddMux->type.in($mux)
+ select nusers(port(postAddMux, \Y)) == 2
+ choice <IdString> AB {\A, \B}
+ index <SigSpec> port(postAddMux, AB) === sigP
+ index <SigSpec> port(postAddMux, \Y) === sigC
+ set postAddMuxAB AB
+ optional
+endmatch
+
+code sigC
+ if (postAddMux)
+ sigC = port(postAddMux, postAddMuxAB == \A ? \B : \A);
+endcode
+
+match overflow
+ if ffP
+ if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET"
+ select overflow->type.in($ge)
+ select GetSize(port(overflow, \Y)) <= 48
+ select port(overflow, \B).is_fully_const()
+ define <Const> B port(overflow, \B).as_const()
+ select std::count(B.bits.begin(), B.bits.end(), State::S1) == 1
+ index <SigSpec> port(overflow, \A) === sigP
+ optional
+endmatch
+
+code
+ accept;
+endcode
+
+// #######################
+
+subpattern in_dffe
+arg argD argQ clock
+
+code
+ dff = nullptr;
+ for (auto c : argQ.chunks()) {
+ if (!c.wire)
+ reject;
+ if (c.wire->get_bool_attribute(\keep))
+ reject;
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
+ reject;
+ }
+endcode
+
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \Q)[offset] === argQ[0]
+
+ // Check that the rest of argQ is present
+ filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
+ filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
+endmatch
+
+code argQ argD
+{
+ if (clock != SigBit() && port(ff, \CLK) != clock)
+ reject;
+
+ SigSpec Q = port(ff, \Q);
+ dff = ff;
+ dffclock = port(ff, \CLK);
+ dffD = argQ;
+ argD = port(ff, \D);
+ argQ = Q;
+ dffD.replace(argQ, argD);
+ // Only search for ffrstmux if dffD only
+ // has two (ff, ffrstmux) users
+ if (nusers(dffD) > 2)
+ argD = SigSpec();
+}
+endcode
+
+match ffrstmux
+ if !argD.empty()
+ select ffrstmux->type.in($mux)
+ index <SigSpec> port(ffrstmux, \Y) === argD
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ define <bool> pol (BA == \B)
+ set ffrstpol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffrstmux) {
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ argD = port(ffrstmux, ffrstpol ? \A : \B);
+ dffD.replace(port(ffrstmux, \Y), argD);
+
+ // Only search for ffcemux if argQ has at
+ // least 3 users (ff, <upstream>, ffrstmux) and
+ // dffD only has two (ff, ffrstmux)
+ if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
+ argD = SigSpec();
+ }
+ else
+ dffrstmux = nullptr;
+endcode
+
+match ffcemux
+ if !argD.empty()
+ select ffcemux->type.in($mux)
+ index <SigSpec> port(ffcemux, \Y) === argD
+ choice <IdString> AB {\A, \B}
+ index <SigSpec> port(ffcemux, AB) === argQ
+ define <bool> pol (AB == \A)
+ set ffcepol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffcemux) {
+ dffcemux = ffcemux;
+ dffcepol = ffcepol;
+ argD = port(ffcemux, ffcepol ? \B : \A);
+ dffD.replace(port(ffcemux, \Y), argD);
+ }
+ else
+ dffcemux = nullptr;
+endcode
+
+// #######################
+
+subpattern out_dffe
+arg argD argQ clock
+
+code
+ dff = nullptr;
+ for (auto c : argD.chunks())
+ if (c.wire->get_bool_attribute(\keep))
+ reject;
+endcode
+
+match ffcemux
+ select ffcemux->type.in($mux)
+ // ffcemux output must have two users: ffcemux and ff.D
+ select nusers(port(ffcemux, \Y)) == 2
+
+ choice <IdString> AB {\A, \B}
+ // keep-last-value net must have at least three users: ffcemux, ff, downstream sink(s)
+ select nusers(port(ffcemux, AB)) >= 3
+
+ slice offset GetSize(port(ffcemux, \Y))
+ define <IdString> BA (AB == \A ? \B : \A)
+ index <SigBit> port(ffcemux, BA)[offset] === argD[0]
+
+ // Check that the rest of argD is present
+ filter GetSize(port(ffcemux, BA)) >= offset + GetSize(argD)
+ filter port(ffcemux, BA).extract(offset, GetSize(argD)) == argD
+
+ set ffoffset offset
+ define <bool> pol (AB == \A)
+ set ffcepol pol
+
+ semioptional
+endmatch
+
+code argD argQ
+ dffcemux = ffcemux;
+ if (ffcemux) {
+ SigSpec BA = port(ffcemux, ffcepol ? \B : \A);
+ SigSpec Y = port(ffcemux, \Y);
+ argQ = argD;
+ argD.replace(BA, Y);
+ argQ.replace(BA, port(ffcemux, ffcepol ? \A : \B));
+
+ dffcemux = ffcemux;
+ dffcepol = ffcepol;
+ }
+endcode
+
+match ffrstmux
+ select ffrstmux->type.in($mux)
+ // ffrstmux output must have two users: ffrstmux and ff.D
+ select nusers(port(ffrstmux, \Y)) == 2
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ slice offset GetSize(port(ffrstmux, \Y))
+ define <IdString> AB (BA == \B ? \A : \B)
+ index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
+
+ // Check that offset is consistent
+ filter !ffcemux || ffoffset == offset
+ // Check that the rest of argD is present
+ filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
+ filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
+
+ set ffoffset offset
+ define <bool> pol (AB == \A)
+ set ffrstpol pol
+
+ semioptional
+endmatch
+
+code argD argQ
+ dffrstmux = ffrstmux;
+ if (ffrstmux) {
+ SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
+ SigSpec Y = port(ffrstmux, \Y);
+ argD.replace(AB, Y);
+
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ }
+endcode
+
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \D)[offset] === argD[0]
+
+ // Check that offset is consistent
+ filter (!ffcemux && !ffrstmux) || ffoffset == offset
+ // Check that the rest of argD is present
+ filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
+ filter port(ff, \D).extract(offset, GetSize(argD)) == argD
+ // Check that FF.Q is connected to CE-mux
+ filter !ffcemux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
+endmatch
+
+code argQ
+ if (ff) {
+ if (clock != SigBit() && port(ff, \CLK) != clock)
+ reject;
+
+ SigSpec D = port(ff, \D);
+ SigSpec Q = port(ff, \Q);
+ if (!ffcemux) {
+ argQ = argD;
+ argQ.replace(D, Q);
+ }
+
+ for (auto c : argQ.chunks()) {
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
+ reject;
+ }
+
+ dff = ff;
+ dffQ = argQ;
+ dffclock = port(ff, \CLK);
+ }
+ // No enable/reset mux possible without flop
+ else if (dffcemux || dffrstmux)
+ reject;
+endcode
diff --git a/passes/pmgen/xilinx_dsp_CREG.pmg b/passes/pmgen/xilinx_dsp_CREG.pmg
new file mode 100644
index 000000000..a31dc80bf
--- /dev/null
+++ b/passes/pmgen/xilinx_dsp_CREG.pmg
@@ -0,0 +1,181 @@
+pattern xilinx_dsp_packC
+
+udata <std::function<SigSpec(const SigSpec&)>> unextend
+state <SigBit> clock
+state <SigSpec> sigC sigP
+state <bool> ffCcepol ffCrstpol
+state <Cell*> ffC ffCcemux ffCrstmux
+
+// subpattern
+state <SigSpec> argQ argD
+state <bool> ffcepol ffrstpol
+state <int> ffoffset
+udata <SigSpec> dffD dffQ
+udata <SigBit> dffclock
+udata <Cell*> dff dffcemux dffrstmux
+udata <bool> dffcepol dffrstpol
+
+match dsp
+ select dsp->type.in(\DSP48E1)
+ select param(dsp, \CREG, 1).as_int() == 0
+ select nusers(port(dsp, \C, SigSpec())) > 1
+endmatch
+
+code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC sigP clock
+ unextend = [](const SigSpec &sig) {
+ int i;
+ for (i = GetSize(sig)-1; i > 0; i--)
+ if (sig[i] != sig[i-1])
+ break;
+ // Do not remove non-const sign bit
+ if (sig[i].wire)
+ ++i;
+ return sig.extract(0, i);
+ };
+ sigC = unextend(port(dsp, \C, SigSpec()));
+
+ SigSpec P = port(dsp, \P);
+ if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") {
+ // Only care about those bits that are used
+ int i;
+ for (i = 0; i < GetSize(P); i++) {
+ if (nusers(P[i]) <= 1)
+ break;
+ sigP.append(P[i]);
+ }
+ log_assert(nusers(P.extract_end(i)) <= 1);
+ }
+ else
+ sigP = P;
+
+ if (sigC == sigP)
+ reject;
+
+ clock = port(dsp, \CLK, SigBit());
+
+ argQ = sigC;
+ subpattern(in_dffe);
+ if (dff) {
+ ffC = dff;
+ clock = dffclock;
+ if (dffrstmux) {
+ ffCrstmux = dffrstmux;
+ ffCrstpol = dffrstpol;
+ }
+ if (dffcemux) {
+ ffCcemux = dffcemux;
+ ffCcepol = dffcepol;
+ }
+ sigC = dffD;
+ }
+endcode
+
+code
+ if (ffC)
+ accept;
+endcode
+
+// #######################
+
+subpattern in_dffe
+arg argD argQ clock
+
+code
+ dff = nullptr;
+ for (auto c : argQ.chunks()) {
+ if (!c.wire)
+ reject;
+ if (c.wire->get_bool_attribute(\keep))
+ reject;
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
+ reject;
+ }
+endcode
+
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \Q)[offset] === argQ[0]
+
+ // Check that the rest of argQ is present
+ filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
+ filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
+endmatch
+
+code argQ argD
+{
+ if (clock != SigBit() && port(ff, \CLK) != clock)
+ reject;
+
+ SigSpec Q = port(ff, \Q);
+ dff = ff;
+ dffclock = port(ff, \CLK);
+ dffD = argQ;
+ argD = port(ff, \D);
+ argQ = Q;
+ dffD.replace(argQ, argD);
+ // Only search for ffrstmux if dffD only
+ // has two (ff, ffrstmux) users
+ if (nusers(dffD) > 2)
+ argD = SigSpec();
+}
+endcode
+
+match ffrstmux
+ if !argD.empty()
+ select ffrstmux->type.in($mux)
+ index <SigSpec> port(ffrstmux, \Y) === argD
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ define <bool> pol (BA == \B)
+ set ffrstpol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffrstmux) {
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ argD = port(ffrstmux, ffrstpol ? \A : \B);
+ dffD.replace(port(ffrstmux, \Y), argD);
+
+ // Only search for ffcemux if argQ has at
+ // least 3 users (ff, <upstream>, ffrstmux) and
+ // dffD only has two (ff, ffrstmux)
+ if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
+ argD = SigSpec();
+ }
+ else
+ dffrstmux = nullptr;
+endcode
+
+match ffcemux
+ if !argD.empty()
+ select ffcemux->type.in($mux)
+ index <SigSpec> port(ffcemux, \Y) === argD
+ choice <IdString> AB {\A, \B}
+ index <SigSpec> port(ffcemux, AB) === argQ
+ define <bool> pol (AB == \A)
+ set ffcepol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffcemux) {
+ dffcemux = ffcemux;
+ dffcepol = ffcepol;
+ argD = port(ffcemux, ffcepol ? \B : \A);
+ dffD.replace(port(ffcemux, \Y), argD);
+ }
+ else
+ dffcemux = nullptr;
+endcode
diff --git a/passes/pmgen/xilinx_dsp_cascade.pmg b/passes/pmgen/xilinx_dsp_cascade.pmg
new file mode 100644
index 000000000..6f4ac5849
--- /dev/null
+++ b/passes/pmgen/xilinx_dsp_cascade.pmg
@@ -0,0 +1,336 @@
+pattern xilinx_dsp_cascade
+
+udata <std::function<SigSpec(const SigSpec&)>> unextend
+udata <vector<std::tuple<Cell*,int,int,int>>> chain longest_chain
+state <Cell*> next
+state <SigSpec> clock
+state <int> AREG BREG
+
+// subpattern
+state <SigSpec> argQ argD
+state <bool> ffcepol ffrstpol
+state <int> ffoffset
+udata <SigSpec> dffD dffQ
+udata <SigBit> dffclock
+udata <Cell*> dff dffcemux dffrstmux
+udata <bool> dffcepol dffrstpol
+
+code
+#define MAX_DSP_CASCADE 20
+endcode
+
+match first
+ select first->type.in(\DSP48E1)
+ select port(first, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("000")
+ select nusers(port(first, \PCOUT, SigSpec())) <= 1
+endmatch
+
+code
+ longest_chain.clear();
+ chain.emplace_back(first, -1, -1, -1);
+ subpattern(tail);
+finally
+ chain.pop_back();
+ log_assert(chain.empty());
+ if (GetSize(longest_chain) > 1) {
+ Cell *dsp = std::get<0>(longest_chain.front());
+
+ Cell *dsp_pcin;
+ int P, AREG, BREG;
+ for (int i = 1; i < GetSize(longest_chain); i++) {
+ std::tie(dsp_pcin,P,AREG,BREG) = longest_chain[i];
+
+ if (i % MAX_DSP_CASCADE > 0) {
+ if (P >= 0) {
+ Wire *cascade = module->addWire(NEW_ID, 48);
+ dsp_pcin->setPort(ID(C), Const(0, 48));
+ dsp_pcin->setPort(ID(PCIN), cascade);
+ dsp->setPort(ID(PCOUT), cascade);
+ add_siguser(cascade, dsp_pcin);
+ add_siguser(cascade, dsp);
+
+ SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
+ if (P == 17)
+ opmode[6] = State::S1;
+ else if (P == 0)
+ opmode[6] = State::S0;
+ else log_abort();
+
+ opmode[5] = State::S0;
+ opmode[4] = State::S1;
+ dsp_pcin->setPort(\OPMODE, opmode);
+
+ log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+ }
+ if (AREG >= 0) {
+ Wire *cascade = module->addWire(NEW_ID, 30);
+ dsp_pcin->setPort(ID(A), Const(0, 30));
+ dsp_pcin->setPort(ID(ACIN), cascade);
+ dsp->setPort(ID(ACOUT), cascade);
+ add_siguser(cascade, dsp_pcin);
+ add_siguser(cascade, dsp);
+
+ dsp->setParam(ID(ACASCREG), AREG);
+ dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE"));
+
+ log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+ }
+ if (BREG >= 0) {
+ Wire *cascade = module->addWire(NEW_ID, 18);
+ dsp_pcin->setPort(ID(B), Const(0, 18));
+ dsp_pcin->setPort(ID(BCIN), cascade);
+ dsp->setPort(ID(BCOUT), cascade);
+ add_siguser(cascade, dsp_pcin);
+ add_siguser(cascade, dsp);
+
+ dsp->setParam(ID(BCASCREG), BREG);
+ dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE"));
+
+ log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+ }
+ }
+ else {
+ log_debug(" Blocking %s -> %s cascade (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
+ }
+
+ dsp = dsp_pcin;
+ }
+
+ accept;
+ }
+endcode
+
+// ------------------------------------------------------------------
+
+subpattern tail
+arg first
+arg next
+
+match nextP
+ select nextP->type.in(\DSP48E1)
+ select !param(nextP, \CREG, State::S1).as_bool()
+ select port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
+ select nusers(port(nextP, \C, SigSpec())) > 1
+ select nusers(port(nextP, \PCIN, SigSpec())) == 0
+ index <SigBit> port(nextP, \C)[0] === port(std::get<0>(chain.back()), \P)[0]
+ semioptional
+endmatch
+
+match nextP_shift17
+ if !nextP
+ select nextP_shift17->type.in(\DSP48E1)
+ select !param(nextP_shift17, \CREG, State::S1).as_bool()
+ select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
+ select nusers(port(nextP_shift17, \C, SigSpec())) > 1
+ select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0
+ index <SigBit> port(nextP_shift17, \C)[0] === port(std::get<0>(chain.back()), \P)[17]
+ semioptional
+endmatch
+
+code next
+ next = nextP;
+ if (!nextP)
+ next = nextP_shift17;
+ if (next) {
+ unextend = [](const SigSpec &sig) {
+ int i;
+ for (i = GetSize(sig)-1; i > 0; i--)
+ if (sig[i] != sig[i-1])
+ break;
+ // Do not remove non-const sign bit
+ if (sig[i].wire)
+ ++i;
+ return sig.extract(0, i);
+ };
+ }
+endcode
+
+code argQ clock AREG
+ AREG = -1;
+ if (next) {
+ Cell *prev = std::get<0>(chain.back());
+ if (param(prev, \AREG, 2).as_int() > 0 &&
+ param(next, \AREG, 2).as_int() > 0 &&
+ param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
+ port(next, \ACIN, SigSpec()).is_fully_zero() &&
+ nusers(port(prev, \ACOUT, SigSpec())) <= 1) {
+ argQ = unextend(port(next, \A));
+ clock = port(prev, \CLK);
+ subpattern(in_dffe);
+ if (dff) {
+ if (!dffrstmux && port(prev, \RSTA, State::S0) != State::S0)
+ goto reject_AREG;
+ if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0))
+ goto reject_AREG;
+ if (!dffcemux && port(prev, \CEA2, State::S0) != State::S0)
+ goto reject_AREG;
+ if (dffcemux && port(dffcemux, \S) != port(prev, \CEA2, State::S0))
+ goto reject_AREG;
+ if (dffD == unextend(port(prev, \A)))
+ AREG = 1;
+reject_AREG: ;
+ }
+ }
+ }
+endcode
+
+code argQ clock BREG
+ BREG = -1;
+ if (next) {
+ Cell *prev = std::get<0>(chain.back());
+ if (param(prev, \BREG, 2).as_int() > 0 &&
+ param(next, \BREG, 2).as_int() > 0 &&
+ param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
+ port(next, \BCIN, SigSpec()).is_fully_zero() &&
+ nusers(port(prev, \BCOUT, SigSpec())) <= 1) {
+ argQ = unextend(port(next, \B));
+ clock = port(prev, \CLK);
+ subpattern(in_dffe);
+ if (dff) {
+ if (!dffrstmux && port(prev, \RSTB, State::S0) != State::S0)
+ goto reject_BREG;
+ if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTB, State::S0))
+ goto reject_BREG;
+ if (!dffcemux && port(prev, \CEB2, State::S0) != State::S0)
+ goto reject_BREG;
+ if (dffcemux && port(dffcemux, \S) != port(prev, \CEB2, State::S0))
+ goto reject_BREG;
+ if (dffD == unextend(port(prev, \B)))
+ BREG = 1;
+reject_BREG: ;
+ }
+ }
+ }
+endcode
+
+code
+ if (next) {
+ chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG);
+
+ SigSpec sigC = unextend(port(next, \C));
+
+ // TODO: Cannot use 'reject' since semioptional
+ if (nextP_shift17) {
+ if (GetSize(sigC)+17 <= GetSize(port(std::get<0>(chain.back()), \P)) &&
+ port(std::get<0>(chain.back()), \P).extract(17, GetSize(sigC)) != sigC)
+ subpattern(tail);
+ }
+ else {
+ if (GetSize(sigC) <= GetSize(port(std::get<0>(chain.back()), \P)) &&
+ port(std::get<0>(chain.back()), \P).extract(0, GetSize(sigC)) != sigC)
+ subpattern(tail);
+
+ }
+ } else {
+ if (GetSize(chain) > GetSize(longest_chain))
+ longest_chain = chain;
+ }
+finally
+ if (next)
+ chain.pop_back();
+endcode
+
+// #######################
+
+subpattern in_dffe
+arg argD argQ clock
+
+code
+ dff = nullptr;
+ for (auto c : argQ.chunks()) {
+ if (!c.wire)
+ reject;
+ if (c.wire->get_bool_attribute(\keep))
+ reject;
+ Const init = c.wire->attributes.at(\init, State::Sx);
+ if (!init.is_fully_undef() && !init.is_fully_zero())
+ reject;
+ }
+endcode
+
+match ff
+ select ff->type.in($dff)
+ // DSP48E1 does not support clock inversion
+ select param(ff, \CLK_POLARITY).as_bool()
+
+ slice offset GetSize(port(ff, \D))
+ index <SigBit> port(ff, \Q)[offset] === argQ[0]
+
+ // Check that the rest of argQ is present
+ filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
+ filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
+
+ set ffoffset offset
+endmatch
+
+code argQ argD
+{
+ if (clock != SigBit() && port(ff, \CLK) != clock)
+ reject;
+
+ SigSpec Q = port(ff, \Q);
+ dff = ff;
+ dffclock = port(ff, \CLK);
+ dffD = argQ;
+ argD = port(ff, \D);
+ argQ = Q;
+ dffD.replace(argQ, argD);
+ // Only search for ffrstmux if dffD only
+ // has two (ff, ffrstmux) users
+ if (nusers(dffD) > 2)
+ argD = SigSpec();
+}
+endcode
+
+match ffrstmux
+ if !argD.empty()
+ select ffrstmux->type.in($mux)
+ index <SigSpec> port(ffrstmux, \Y) === argD
+
+ choice <IdString> BA {\B, \A}
+ // DSP48E1 only supports reset to zero
+ select port(ffrstmux, BA).is_fully_zero()
+
+ define <bool> pol (BA == \B)
+ set ffrstpol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffrstmux) {
+ dffrstmux = ffrstmux;
+ dffrstpol = ffrstpol;
+ argD = port(ffrstmux, ffrstpol ? \A : \B);
+ dffD.replace(port(ffrstmux, \Y), argD);
+
+ // Only search for ffcemux if argQ has at
+ // least 3 users (ff, <upstream>, ffrstmux) and
+ // dffD only has two (ff, ffrstmux)
+ if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
+ argD = SigSpec();
+ }
+ else
+ dffrstmux = nullptr;
+endcode
+
+match ffcemux
+ if !argD.empty()
+ select ffcemux->type.in($mux)
+ index <SigSpec> port(ffcemux, \Y) === argD
+ choice <IdString> AB {\A, \B}
+ index <SigSpec> port(ffcemux, AB) === argQ
+ define <bool> pol (AB == \A)
+ set ffcepol pol
+ semioptional
+endmatch
+
+code argD
+ if (ffcemux) {
+ dffcemux = ffcemux;
+ dffcepol = ffcepol;
+ argD = port(ffcemux, ffcepol ? \B : \A);
+ dffD.replace(port(ffcemux, \Y), argD);
+ }
+ else
+ dffcemux = nullptr;
+endcode
diff --git a/passes/pmgen/xilinx_srl.pmg b/passes/pmgen/xilinx_srl.pmg
index b18119b87..535b3dfdc 100644
--- a/passes/pmgen/xilinx_srl.pmg
+++ b/passes/pmgen/xilinx_srl.pmg
@@ -13,9 +13,9 @@ 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()
+ select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
+ select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
+ select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
filter !non_first_cells.count(first)
generate
SigSpec C = module->addWire(NEW_ID);
@@ -84,9 +84,9 @@ 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()
+ select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
+ select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
+ select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
endmatch
code clk_port en_port
@@ -111,10 +111,10 @@ match next
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()
+ filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
endmatch
code
@@ -138,10 +138,10 @@ match next
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()
+ filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
+ filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
generate
Cell *cell = module->addCell(NEW_ID, chain.back()->type);
cell->setPort(\C, chain.back()->getPort(\C));
@@ -149,7 +149,7 @@ generate
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(\R, port(chain.back(), \R, State::S0));
cell->setPort(\CE, chain.back()->getPort(\CE));
}
else if (cell->type.begins_with("$_DFFE_"))
diff --git a/passes/sat/async2sync.cc b/passes/sat/async2sync.cc
index 24ae6e448..740248545 100644
--- a/passes/sat/async2sync.cc
+++ b/passes/sat/async2sync.cc
@@ -198,6 +198,7 @@ struct Async2syncPass : public Pass {
module->addMux(NEW_ID, sig_d, new_q, sig_en, sig_q);
}
+ cell->setPort("\\D", sig_q);
cell->setPort("\\Q", new_q);
cell->unsetPort("\\EN");
cell->unsetParam("\\EN_POLARITY");
diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc
index 631a80aa5..cd357d72a 100644
--- a/passes/techmap/Makefile.inc
+++ b/passes/techmap/Makefile.inc
@@ -40,6 +40,7 @@ OBJS += passes/techmap/attrmap.o
OBJS += passes/techmap/zinit.o
OBJS += passes/techmap/dff2dffs.o
OBJS += passes/techmap/flowmap.o
+OBJS += passes/techmap/extractinv.o
endif
GENFILES += passes/techmap/techmap.inc
diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc
index 6fdf987f0..09d6e9670 100644
--- a/passes/techmap/abc9.cc
+++ b/passes/techmap/abc9.cc
@@ -76,8 +76,7 @@ inline std::string remap_name(RTLIL::IdString abc_name)
return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1);
}
-void handle_loops(RTLIL::Design *design,
- const dict<IdString,pool<IdString>> &scc_break_inputs)
+void handle_loops(RTLIL::Design *design)
{
Pass::call(design, "scc -set_attr abc_scc_id {}");
@@ -114,30 +113,6 @@ void handle_loops(RTLIL::Design *design,
}
cell->attributes.erase(it);
}
-
- auto jt = scc_break_inputs.find(cell->type);
- if (jt != scc_break_inputs.end())
- for (auto port_name : jt->second) {
- RTLIL::SigSpec sig;
- auto &rhs = cell->connections_.at(port_name);
- for (auto b : rhs) {
- Wire *w = b.wire;
- if (!w) continue;
- w->port_output = true;
- w->set_bool_attribute(ID(abc_scc_break));
- w = module->wire(stringf("%s.abci", w->name.c_str()));
- if (!w) {
- w = module->addWire(stringf("%s.abci", b.wire->name.c_str()), GetSize(b.wire));
- w->port_input = true;
- }
- else {
- log_assert(b.offset < GetSize(w));
- log_assert(w->port_input);
- }
- sig.append(RTLIL::SigBit(w, b.offset));
- }
- rhs = sig;
- }
}
module->fixup_ports();
@@ -272,8 +247,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str,
bool /*keepff*/, 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, const dict<int,IdString> &box_lookup,
- const dict<IdString,pool<IdString>> &scc_break_inputs
+ std::string wire_delay, const dict<int,IdString> &box_lookup
)
{
module = current_module;
@@ -413,7 +387,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
RTLIL::Selection& sel = design->selection_stack.back();
sel.select(module);
- handle_loops(design, scc_break_inputs);
+ handle_loops(design);
Pass::call(design, "aigmap");
@@ -497,7 +471,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
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);
+ ifs.open(buffer, std::ifstream::binary);
if (ifs.fail())
log_error("Can't open ABC output file `%s'.\n", buffer.c_str());
@@ -632,7 +606,6 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
existing_cell = module->cell(c->name);
log_assert(existing_cell);
cell = module->addCell(remap_name(c->name), c->type);
- module->swap_names(cell, existing_cell);
}
if (markgroups) cell->attributes[ID(abcgroup)] = map_autoidx;
@@ -668,8 +641,22 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
}
}
- for (auto cell : boxes)
- module->remove(cell);
+ 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(abc_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()) {
@@ -1050,9 +1037,6 @@ struct Abc9Pass : public Pass {
}
if (arg == "-box" && argidx+1 < args.size()) {
box_file = args[++argidx];
- rewrite_filename(box_file);
- if (!box_file.empty() && !is_absolute_path(box_file))
- box_file = std::string(pwd) + "/" + box_file;
continue;
}
if (arg == "-W" && argidx+1 < args.size()) {
@@ -1063,8 +1047,15 @@ struct Abc9Pass : public Pass {
}
extra_args(args, argidx, design);
+ // ABC expects a box file for XAIG
+ if (box_file.empty())
+ box_file = "+/dummy.box";
+
+ rewrite_filename(box_file);
+ if (!box_file.empty() && !is_absolute_path(box_file))
+ box_file = std::string(pwd) + "/" + box_file;
+
dict<int,IdString> box_lookup;
- dict<IdString,pool<IdString>> scc_break_inputs;
for (auto m : design->modules()) {
auto it = m->attributes.find(ID(abc_box_id));
if (it == m->attributes.end())
@@ -1082,17 +1073,13 @@ struct Abc9Pass : public Pass {
for (auto p : m->ports) {
auto w = m->wire(p);
log_assert(w);
- if (w->port_input) {
- if (w->attributes.count(ID(abc_scc_break)))
- scc_break_inputs[m->name].insert(p);
- if (w->attributes.count(ID(abc_carry))) {
+ if (w->attributes.count(ID(abc_carry))) {
+ if (w->port_input) {
if (carry_in)
log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m));
carry_in = w;
}
- }
- if (w->port_output) {
- if (w->attributes.count(ID(abc_carry))) {
+ else if (w->port_output) {
if (carry_out)
log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m));
carry_out = w;
@@ -1144,7 +1131,7 @@ struct Abc9Pass : public Pass {
if (!dff_mode || !clk_str.empty()) {
abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, dff_mode, clk_str, keepff,
delay_target, lutin_shared, fast_mode, show_tempdir,
- box_file, lut_file, wire_delay, box_lookup, scc_break_inputs);
+ box_file, lut_file, wire_delay, box_lookup);
continue;
}
@@ -1290,7 +1277,7 @@ struct Abc9Pass : public Pass {
en_sig = assign_map(std::get<3>(it.first));
abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, !clk_sig.empty(), "$",
keepff, delay_target, lutin_shared, fast_mode, show_tempdir,
- box_file, lut_file, wire_delay, box_lookup, scc_break_inputs);
+ box_file, lut_file, wire_delay, box_lookup);
assign_map.set(mod);
}
}
diff --git a/passes/techmap/alumacc.cc b/passes/techmap/alumacc.cc
index 5b168d524..034731b87 100644
--- a/passes/techmap/alumacc.cc
+++ b/passes/techmap/alumacc.cc
@@ -48,14 +48,25 @@ struct AlumaccWorker
RTLIL::SigSpec cached_cf, cached_of, cached_sf;
RTLIL::SigSpec get_lt() {
- if (GetSize(cached_lt) == 0)
- cached_lt = is_signed ? alu_cell->module->Xor(NEW_ID, get_of(), get_sf()) : get_cf();
+ if (GetSize(cached_lt) == 0) {
+ if (is_signed) {
+ get_of();
+ get_sf();
+ cached_lt = alu_cell->module->Xor(NEW_ID, cached_of, cached_sf);
+ }
+ else
+ cached_lt = get_cf();
+ }
return cached_lt;
}
RTLIL::SigSpec get_gt() {
- if (GetSize(cached_gt) == 0)
- cached_gt = alu_cell->module->Not(NEW_ID, alu_cell->module->Or(NEW_ID, get_lt(), get_eq()), false, alu_cell->get_src_attribute());
+ if (GetSize(cached_gt) == 0) {
+ get_lt();
+ get_eq();
+ SigSpec Or = alu_cell->module->Or(NEW_ID, cached_lt, cached_eq);
+ cached_gt = alu_cell->module->Not(NEW_ID, Or, false, alu_cell->get_src_attribute());
+ }
return cached_gt;
}
diff --git a/passes/techmap/dff2dffs.cc b/passes/techmap/dff2dffs.cc
index 0ea033513..3fa1ed5cf 100644
--- a/passes/techmap/dff2dffs.cc
+++ b/passes/techmap/dff2dffs.cc
@@ -34,11 +34,16 @@ struct Dff2dffsPass : public Pass {
log("Merge synchronous set/reset $_MUX_ cells to create $__DFFS_[NP][NP][01], to be run before\n");
log("dff2dffe for SR over CE priority.\n");
log("\n");
+ log(" -match-init\n");
+ log(" Disallow merging synchronous set/reset that has polarity opposite of the\n");
+ log(" output wire's init attribute (if any).\n");
+ log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
log_header(design, "Executing dff2dffs pass (merge synchronous set/reset into FF cells).\n");
+ bool match_init = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
@@ -46,6 +51,10 @@ struct Dff2dffsPass : public Pass {
// singleton_mode = true;
// continue;
// }
+ if (args[argidx] == "-match-init") {
+ match_init = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
@@ -96,9 +105,6 @@ struct Dff2dffsPass : public Pass {
SigBit bit_b = sigmap(mux_cell->getPort(ID::B));
SigBit bit_s = sigmap(mux_cell->getPort(ID(S)));
- log(" Merging %s (A=%s, B=%s, S=%s) into %s (%s).\n", log_id(mux_cell),
- log_signal(bit_a), log_signal(bit_b), log_signal(bit_s), log_id(cell), log_id(cell->type));
-
SigBit sr_val, sr_sig;
bool invert_sr;
sr_sig = bit_s;
@@ -113,6 +119,23 @@ struct Dff2dffsPass : public Pass {
invert_sr = false;
}
+ if (match_init) {
+ SigBit bit_q = cell->getPort(ID(Q));
+ if (bit_q.wire) {
+ auto it = bit_q.wire->attributes.find(ID(init));
+ if (it != bit_q.wire->attributes.end()) {
+ auto init_val = it->second[bit_q.offset];
+ if (init_val == State::S1 && sr_val != State::S1)
+ continue;
+ if (init_val == State::S0 && sr_val != State::S0)
+ continue;
+ }
+ }
+ }
+
+ log(" Merging %s (A=%s, B=%s, S=%s) into %s (%s).\n", log_id(mux_cell),
+ log_signal(bit_a), log_signal(bit_b), log_signal(bit_s), log_id(cell), log_id(cell->type));
+
if (sr_val == State::S1) {
if (cell->type == ID($_DFF_N_)) {
if (invert_sr) cell->type = ID($__DFFS_NN1_);
diff --git a/passes/techmap/extractinv.cc b/passes/techmap/extractinv.cc
new file mode 100644
index 000000000..dda71f12a
--- /dev/null
+++ b/passes/techmap/extractinv.cc
@@ -0,0 +1,123 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2019 Marcin Koƛcielnicki <mwk@0x04.net>
+ *
+ * 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
+
+void split_portname_pair(std::string &port1, std::string &port2)
+{
+ size_t pos = port1.find_first_of(':');
+ if (pos != std::string::npos) {
+ port2 = port1.substr(pos+1);
+ port1 = port1.substr(0, pos);
+ }
+}
+
+struct ExtractinvPass : public Pass {
+ ExtractinvPass() : Pass("extractinv", "extract explicit inverter cells for invertible cell pins") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" extractinv [options] [selection]\n");
+ log("\n");
+ log("Searches the design for all cells with invertible pins controlled by a cell\n");
+ log("parameter (eg. IS_CLK_INVERTED on many Xilinx cells) and removes the parameter.\n");
+ log("If the parameter was set to 1, inserts an explicit inverter cell in front of\n");
+ log("the pin instead. Normally used for output to ISE, which does not support the\n");
+ log("inversion parameters.\n");
+ log("\n");
+ log("To mark a cell port as invertible, use (* invertible_pin = \"param_name\" *)\n");
+ log("on the wire in the blackbox module. The parameter value should have\n");
+ log("the same width as the port, and will be effectively XORed with it.\n");
+ log("\n");
+ log(" -inv <celltype> <portname_out>:<portname_in>\n");
+ log(" Specifies the cell type to use for the inverters and its port names.\n");
+ log(" This option is required.\n");
+ log("\n");
+ }
+
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ log_header(design, "Executing EXTRACTINV pass (extracting pin inverters).\n");
+
+ std::string inv_celltype, inv_portname, inv_portname2;
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++)
+ {
+ std::string arg = args[argidx];
+ if (arg == "-inv" && argidx+2 < args.size()) {
+ inv_celltype = args[++argidx];
+ inv_portname = args[++argidx];
+ split_portname_pair(inv_portname, inv_portname2);
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ if (inv_celltype.empty())
+ log_error("The -inv option is required.\n");
+
+ for (auto module : design->selected_modules())
+ {
+ for (auto cell : module->selected_cells())
+ for (auto port : cell->connections()) {
+ auto cell_module = design->module(cell->type);
+ if (!cell_module)
+ continue;
+ auto cell_wire = cell_module->wire(port.first);
+ if (!cell_wire)
+ continue;
+ auto it = cell_wire->attributes.find("\\invertible_pin");
+ if (it == cell_wire->attributes.end())
+ continue;
+ IdString param_name = RTLIL::escape_id(it->second.decode_string());
+ auto it2 = cell->parameters.find(param_name);
+ // Inversion not used -- skip.
+ if (it2 == cell->parameters.end())
+ continue;
+ SigSpec sig = port.second;
+ if (it2->second.size() != sig.size())
+ log_error("The inversion parameter needs to be the same width as the port (%s.%s port %s parameter %s)", log_id(module->name), log_id(cell->type), log_id(port.first), log_id(param_name));
+ RTLIL::Const invmask = it2->second;
+ cell->parameters.erase(param_name);
+ if (invmask.is_fully_zero())
+ continue;
+ Wire *iwire = module->addWire(NEW_ID, sig.size());
+ for (int i = 0; i < sig.size(); i++)
+ if (invmask[i] == State::S1) {
+ RTLIL::Cell *icell = module->addCell(NEW_ID, RTLIL::escape_id(inv_celltype));
+ icell->setPort(RTLIL::escape_id(inv_portname), SigSpec(iwire, i));
+ icell->setPort(RTLIL::escape_id(inv_portname2), sig[i]);
+ log("Inserting %s on %s.%s.%s[%d].\n", inv_celltype.c_str(), log_id(module), log_id(cell->type), log_id(port.first), i);
+ sig[i] = SigBit(iwire, i);
+ }
+ cell->setPort(port.first, sig);
+ }
+ }
+ }
+} ExtractinvPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc
index c4496f76f..08a1af2d5 100644
--- a/passes/techmap/techmap.cc
+++ b/passes/techmap/techmap.cc
@@ -205,20 +205,57 @@ struct TechmapWorker
}
std::map<RTLIL::IdString, RTLIL::IdString> positional_ports;
+ dict<Wire*, IdString> temp_renamed_wires;
+ pool<SigBit> autopurge_tpl_bits;
- for (auto &it : tpl->wires_) {
+ for (auto &it : tpl->wires_)
+ {
if (it.second->port_id > 0)
- positional_ports[stringf("$%d", it.second->port_id)] = it.first;
+ {
+ IdString posportname = stringf("$%d", it.second->port_id);
+ positional_ports[posportname] = it.first;
+
+ if (!flatten_mode && it.second->get_bool_attribute(ID(techmap_autopurge)) &&
+ (!cell->hasPort(it.second->name) || !GetSize(cell->getPort(it.second->name))) &&
+ (!cell->hasPort(posportname) || !GetSize(cell->getPort(posportname))))
+ {
+ if (sigmaps.count(tpl) == 0)
+ sigmaps[tpl].set(tpl);
+
+ for (auto bit : sigmaps.at(tpl)(it.second))
+ if (bit.wire != nullptr)
+ autopurge_tpl_bits.insert(bit);
+ }
+ }
IdString w_name = it.second->name;
apply_prefix(cell->name, w_name);
- RTLIL::Wire *w = module->addWire(w_name, it.second);
- w->port_input = false;
- w->port_output = false;
- w->port_id = 0;
- if (it.second->get_bool_attribute(ID(_techmap_special_)))
- w->attributes.clear();
- if (w->attributes.count(ID(src)))
- w->add_strpool_attribute(ID(src), extra_src_attrs);
+ RTLIL::Wire *w = module->wire(w_name);
+ if (w != nullptr) {
+ if (!flatten_mode || !w->get_bool_attribute(ID(hierconn))) {
+ temp_renamed_wires[w] = w->name;
+ module->rename(w, NEW_ID);
+ w = nullptr;
+ } else {
+ w->attributes.erase(ID(hierconn));
+ if (GetSize(w) < GetSize(it.second)) {
+ log_warning("Widening signal %s.%s to match size of %s.%s (via %s.%s).\n", log_id(module), log_id(w),
+ log_id(tpl), log_id(it.second), log_id(module), log_id(cell));
+ w->width = GetSize(it.second);
+ }
+ }
+ }
+ if (w == nullptr) {
+ w = module->addWire(w_name, it.second);
+ w->port_input = false;
+ w->port_output = false;
+ w->port_id = 0;
+ if (!flatten_mode)
+ w->attributes.erase(ID(techmap_autopurge));
+ if (it.second->get_bool_attribute(ID(_techmap_special_)))
+ w->attributes.clear();
+ if (w->attributes.count(ID(src)))
+ w->add_strpool_attribute(ID(src), extra_src_attrs);
+ }
design->select(module, w);
}
@@ -322,6 +359,12 @@ struct TechmapWorker
for (auto &attr : w->attributes) {
if (attr.first == ID(src))
continue;
+ auto lhs = GetSize(extra_connect.first);
+ auto rhs = GetSize(extra_connect.second);
+ if (lhs > rhs)
+ extra_connect.first.remove(rhs, lhs-rhs);
+ else if (rhs > lhs)
+ extra_connect.second.remove(lhs, rhs-lhs);
module->connect(extra_connect);
break;
}
@@ -344,11 +387,31 @@ struct TechmapWorker
if (!flatten_mode && c->type.begins_with("\\$"))
c->type = c->type.substr(1);
- for (auto &it2 : c->connections_) {
- apply_prefix(cell->name, it2.second, module);
- port_signal_map.apply(it2.second);
+ vector<IdString> autopurge_ports;
+
+ for (auto &it2 : c->connections_)
+ {
+ bool autopurge = false;
+ if (!autopurge_tpl_bits.empty()) {
+ autopurge = GetSize(it2.second) != 0;
+ for (auto &bit : sigmaps.at(tpl)(it2.second))
+ if (!autopurge_tpl_bits.count(bit)) {
+ autopurge = false;
+ break;
+ }
+ }
+
+ if (autopurge) {
+ autopurge_ports.push_back(it2.first);
+ } else {
+ apply_prefix(cell->name, it2.second, module);
+ port_signal_map.apply(it2.second);
+ }
}
+ for (auto &it2 : autopurge_ports)
+ c->unsetPort(it2);
+
if (c->type.in(ID($memrd), ID($memwr), ID($meminit))) {
IdString memid = c->getParam(ID(MEMID)).decode_string();
log_assert(memory_renames.count(memid) != 0);
@@ -380,6 +443,16 @@ struct TechmapWorker
}
module->remove(cell);
+
+ for (auto &it : temp_renamed_wires)
+ {
+ Wire *w = it.first;
+ IdString name = it.second;
+ IdString altname = module->uniquify(name);
+ Wire *other_w = module->wire(name);
+ module->rename(other_w, altname);
+ module->rename(w, name);
+ }
}
bool techmap_module(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Design *map, std::set<RTLIL::Cell*> &handled_cells,
@@ -396,6 +469,18 @@ struct TechmapWorker
SigMap sigmap(module);
+ dict<SigBit, State> init_bits;
+ pool<SigBit> remove_init_bits;
+
+ for (auto wire : module->wires()) {
+ if (wire->attributes.count("\\init")) {
+ Const value = wire->attributes.at("\\init");
+ for (int i = 0; i < min(GetSize(value), GetSize(wire)); i++)
+ if (value[i] != State::Sx)
+ init_bits[sigmap(SigBit(wire, i))] = value[i];
+ }
+ }
+
TopoSort<RTLIL::Cell*, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> cells;
std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_inbit;
std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> outbit_to_cell;
@@ -633,6 +718,17 @@ struct TechmapWorker
bit = RTLIL::SigBit(RTLIL::State::Sx);
parameters[stringf("\\_TECHMAP_CONSTVAL_%s_", RTLIL::id2cstr(conn.first))] = RTLIL::SigSpec(v).as_const();
}
+ if (tpl->avail_parameters.count(stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))) != 0) {
+ auto sig = sigmap(conn.second);
+ RTLIL::Const value(State::Sx, sig.size());
+ for (int i = 0; i < sig.size(); i++) {
+ auto it = init_bits.find(sig[i]);
+ if (it != init_bits.end()) {
+ value[i] = it->second;
+ }
+ }
+ parameters[stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))] = value;
+ }
}
int unique_bit_id_counter = 0;
@@ -833,7 +929,7 @@ struct TechmapWorker
TechmapWires twd = techmap_find_special_wires(tpl);
for (auto &it : twd) {
- if (it.first != "_TECHMAP_FAIL_" && it.first.substr(0, 12) != "_TECHMAP_DO_" && it.first.substr(0, 14) != "_TECHMAP_DONE_")
+ if (it.first != "_TECHMAP_FAIL_" && (it.first.substr(0, 20) != "_TECHMAP_REMOVEINIT_" || it.first[it.first.size()-1] != '_') && it.first.substr(0, 12) != "_TECHMAP_DO_" && it.first.substr(0, 14) != "_TECHMAP_DONE_")
log_error("Techmap yielded unknown config wire %s.\n", it.first.c_str());
if (techmap_do_cache[tpl])
for (auto &it2 : it.second)
@@ -864,6 +960,23 @@ struct TechmapWorker
mkdebug.off();
}
+ TechmapWires twd = techmap_find_special_wires(tpl);
+ for (auto &it : twd) {
+ if (it.first.substr(0, 20) == "_TECHMAP_REMOVEINIT_") {
+ for (auto &it2 : it.second) {
+ auto val = it2.value.as_const();
+ auto wirename = RTLIL::escape_id(it.first.substr(20, it.first.size() - 20 - 1));
+ auto it = cell->connections().find(wirename);
+ if (it != cell->connections().end()) {
+ auto sig = sigmap(it->second);
+ for (int i = 0; i < sig.size(); i++)
+ if (val[i] == State::S1)
+ remove_init_bits.insert(sig[i]);
+ }
+ }
+ }
+ }
+
if (extern_mode && !in_recursion)
{
std::string m_name = stringf("$extern:%s", log_id(tpl));
@@ -907,6 +1020,25 @@ struct TechmapWorker
handled_cells.insert(cell);
}
+ if (!remove_init_bits.empty()) {
+ for (auto wire : module->wires())
+ if (wire->attributes.count("\\init")) {
+ Const &value = wire->attributes.at("\\init");
+ bool do_cleanup = true;
+ for (int i = 0; i < min(GetSize(value), GetSize(wire)); i++) {
+ SigBit bit = sigmap(SigBit(wire, i));
+ if (remove_init_bits.count(bit))
+ value[i] = State::Sx;
+ else if (value[i] != State::Sx)
+ do_cleanup = false;
+ }
+ if (do_cleanup) {
+ log("Removing init attribute from wire %s.%s.\n", log_id(module), log_id(wire));
+ wire->attributes.erase("\\init");
+ }
+ }
+ }
+
if (log_continue) {
log_header(design, "Continuing TECHMAP pass.\n");
log_continue = false;
@@ -981,6 +1113,11 @@ struct TechmapPass : public Pass {
log("will create a wrapper for the cell and then run the command string that the\n");
log("attribute is set to on the wrapper module.\n");
log("\n");
+ log("When a port on a module in the map file has the 'techmap_autopurge' attribute\n");
+ log("set, and that port is not connected in the instantiation that is mapped, then\n");
+ log("then a cell port connected only to such wires will be omitted in the mapped\n");
+ log("version of the circuit.\n");
+ log("\n");
log("All wires in the modules from the map file matching the pattern _TECHMAP_*\n");
log("or *._TECHMAP_* are special wires that are used to pass instructions from\n");
log("the mapping module to the techmap command. At the moment the following special\n");
@@ -1019,6 +1156,13 @@ struct TechmapPass : public Pass {
log("\n");
log(" It is possible to combine both prefixes to 'RECURSION; CONSTMAP; '.\n");
log("\n");
+ log(" _TECHMAP_REMOVEINIT_<port-name>_\n");
+ log(" When this wire is set to a constant value, the init attribute of the wire(s)\n");
+ log(" connected to this port will be consumed. This wire must have the same\n");
+ log(" width as the given port, and for every bit that is set to 1 in the value,\n");
+ log(" the corresponding init attribute bit will be changed to 1'bx. If all\n");
+ log(" bits of an init attribute are left as x, it will be removed.\n");
+ log("\n");
log("In addition to this special wires, techmap also supports special parameters in\n");
log("modules in the map file:\n");
log("\n");
@@ -1032,6 +1176,13 @@ struct TechmapPass : public Pass {
log(" former has a 1-bit for each constant input bit and the latter has the\n");
log(" value for this bit. The unused bits of the latter are set to undef (x).\n");
log("\n");
+ log(" _TECHMAP_WIREINIT_<port-name>_\n");
+ log(" When a parameter with this name exists, it will be set to the initial\n");
+ log(" value of the wire(s) connected to the given port, as specified by the init\n");
+ log(" attribute. If the attribute doesn't exist, x will be filled for the\n");
+ log(" missing bits. To remove the init attribute bits used, use the\n");
+ log(" _TECHMAP_REMOVEINIT_*_ wires.\n");
+ log("\n");
log(" _TECHMAP_BITS_CONNMAP_\n");
log(" _TECHMAP_CONNMAP_<port-name>_\n");
log(" For an N-bit port, the _TECHMAP_CONNMAP_<port-name>_ parameter, if it\n");
diff --git a/passes/tests/test_autotb.cc b/passes/tests/test_autotb.cc
index bfb1d6642..2b6a86c25 100644
--- a/passes/tests/test_autotb.cc
+++ b/passes/tests/test_autotb.cc
@@ -345,9 +345,17 @@ struct TestAutotbBackend : public Backend {
log("value after initialization. This can e.g. be used to force a reset signal\n");
log("low in order to explore more inner states in a state machine.\n");
log("\n");
+ log("The attribute 'gentb_skip' can be attached to modules to suppress testbench\n");
+ log("generation.\n");
+ log("\n");
log(" -n <int>\n");
log(" number of iterations the test bench should run (default = 1000)\n");
log("\n");
+ log(" -seed <int>\n");
+ log(" seed used for pseudo-random number generation (default = 0).\n");
+ log(" a value of 0 will cause an arbitrary seed to be chosen, based on\n");
+ log(" the current system time.\n");
+ log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{