aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--passes/opt/Makefile.inc1
-rw-r--r--passes/opt/pmux2shiftx.cc831
-rw-r--r--passes/techmap/shregmap.cc164
-rw-r--r--techlibs/xilinx/cells_map.v128
-rw-r--r--techlibs/xilinx/cells_sim.v39
-rw-r--r--techlibs/xilinx/cells_xtra.sh4
-rw-r--r--techlibs/xilinx/cells_xtra.v16
-rw-r--r--techlibs/xilinx/synth_xilinx.cc40
-rw-r--r--tests/various/pmux2shiftx.v34
-rw-r--r--tests/various/pmux2shiftx.ys28
11 files changed, 1257 insertions, 29 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 42e01645e..36b64e111 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@ Yosys 0.8 .. Yosys 0.8-dev
- Added "gate2lut.v" techmap rule
- Added "rename -src"
- Added "equiv_opt" pass
+ - "synth_xilinx" to now infer hard shift registers, using new "shregmap -tech xilinx"
Yosys 0.7 .. Yosys 0.8
diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc
index c3e0a2a40..337fee9e4 100644
--- a/passes/opt/Makefile.inc
+++ b/passes/opt/Makefile.inc
@@ -13,5 +13,6 @@ OBJS += passes/opt/wreduce.o
OBJS += passes/opt/opt_demorgan.o
OBJS += passes/opt/rmports.o
OBJS += passes/opt/opt_lut.o
+OBJS += passes/opt/pmux2shiftx.o
endif
diff --git a/passes/opt/pmux2shiftx.cc b/passes/opt/pmux2shiftx.cc
new file mode 100644
index 000000000..5f897b131
--- /dev/null
+++ b/passes/opt/pmux2shiftx.cc
@@ -0,0 +1,831 @@
+/*
+ * 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 OnehotDatabase
+{
+ Module *module;
+ const SigMap &sigmap;
+ bool verbose = false;
+ bool initialized = false;
+
+ pool<SigBit> init_ones;
+ dict<SigSpec, pool<SigSpec>> sig_sources_db;
+ dict<SigSpec, bool> sig_onehot_cache;
+ pool<SigSpec> recursion_guard;
+
+ OnehotDatabase(Module *module, const SigMap &sigmap) : module(module), sigmap(sigmap)
+ {
+ }
+
+ void initialize()
+ {
+ log_assert(!initialized);
+ initialized = true;
+
+ for (auto wire : module->wires())
+ {
+ auto it = wire->attributes.find("\\init");
+ if (it == wire->attributes.end())
+ continue;
+
+ auto &val = it->second;
+ int width = std::max(GetSize(wire), GetSize(val));
+
+ for (int i = 0; i < width; i++)
+ if (val[i] == State::S1)
+ init_ones.insert(sigmap(SigBit(wire, i)));
+ }
+
+ for (auto cell : module->cells())
+ {
+ vector<SigSpec> inputs;
+ SigSpec output;
+
+ if (cell->type.in("$adff", "$dff", "$dffe", "$dlatch", "$ff"))
+ {
+ output = cell->getPort("\\Q");
+ if (cell->type == "$adff")
+ inputs.push_back(cell->getParam("\\ARST_VALUE"));
+ inputs.push_back(cell->getPort("\\D"));
+ }
+
+ if (cell->type.in("$mux", "$pmux"))
+ {
+ output = cell->getPort("\\Y");
+ inputs.push_back(cell->getPort("\\A"));
+ SigSpec B = cell->getPort("\\B");
+ for (int i = 0; i < GetSize(B); i += GetSize(output))
+ inputs.push_back(B.extract(i, GetSize(output)));
+ }
+
+ if (!output.empty())
+ {
+ output = sigmap(output);
+ auto &srcs = sig_sources_db[output];
+ for (auto src : inputs) {
+ while (!src.empty() && src[GetSize(src)-1] == State::S0)
+ src.remove(GetSize(src)-1);
+ srcs.insert(sigmap(src));
+ }
+ }
+ }
+ }
+
+ void query_worker(const SigSpec &sig, bool &retval, bool &cache, int indent)
+ {
+ if (verbose)
+ log("%*s %s\n", indent, "", log_signal(sig));
+ log_assert(retval);
+
+ if (recursion_guard.count(sig)) {
+ if (verbose)
+ log("%*s - recursion\n", indent, "");
+ cache = false;
+ return;
+ }
+
+ auto it = sig_onehot_cache.find(sig);
+ if (it != sig_onehot_cache.end()) {
+ if (verbose)
+ log("%*s - cached (%s)\n", indent, "", it->second ? "true" : "false");
+ if (!it->second)
+ retval = false;
+ return;
+ }
+
+ bool found_init_ones = false;
+ for (auto bit : sig) {
+ if (init_ones.count(bit)) {
+ if (found_init_ones) {
+ if (verbose)
+ log("%*s - non-onehot init value\n", indent, "");
+ retval = false;
+ break;
+ }
+ found_init_ones = true;
+ }
+ }
+
+ if (retval)
+ {
+ if (sig.is_fully_const())
+ {
+ bool found_ones = false;
+ for (auto bit : sig) {
+ if (bit == State::S1) {
+ if (found_ones) {
+ if (verbose)
+ log("%*s - non-onehot constant\n", indent, "");
+ retval = false;
+ break;
+ }
+ found_ones = true;
+ }
+ }
+ }
+ else
+ {
+ auto srcs = sig_sources_db.find(sig);
+ if (srcs == sig_sources_db.end()) {
+ if (verbose)
+ log("%*s - no sources for non-const signal\n", indent, "");
+ retval = false;
+ } else {
+ for (auto &src : srcs->second) {
+ bool child_cache = true;
+ recursion_guard.insert(sig);
+ query_worker(src, retval, child_cache, indent+4);
+ recursion_guard.erase(sig);
+ if (!child_cache)
+ cache = false;
+ if (!retval)
+ break;
+ }
+ }
+ }
+ }
+
+ // it is always safe to cache a negative result
+ if (cache || !retval)
+ sig_onehot_cache[sig] = retval;
+ }
+
+ bool query(const SigSpec &sig)
+ {
+ bool retval = true;
+ bool cache = true;
+
+ if (verbose)
+ log("** ONEHOT QUERY START (%s)\n", log_signal(sig));
+
+ if (!initialized)
+ initialize();
+
+ query_worker(sig, retval, cache, 3);
+
+ if (verbose)
+ log("** ONEHOT QUERY RESULT = %s\n", retval ? "true" : "false");
+
+ // it is always safe to cache the root result of a query
+ if (!cache)
+ sig_onehot_cache[sig] = retval;
+
+ return retval;
+ }
+};
+
+struct Pmux2ShiftxPass : public Pass {
+ Pmux2ShiftxPass() : Pass("pmux2shiftx", "transform $pmux cells to $shiftx cells") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" pmux2shiftx [options] [selection]\n");
+ log("\n");
+ log("This pass transforms $pmux cells to $shiftx cells.\n");
+ log("\n");
+ log(" -v, -vv\n");
+ log(" verbose output\n");
+ log("\n");
+ log(" -min_density <percentage>\n");
+ log(" specifies the minimum density for the shifter\n");
+ log(" default: 50\n");
+ log("\n");
+ log(" -min_choices <int>\n");
+ log(" specified the minimum number of choices for a control signal\n");
+ log(" default: 3\n");
+ log("\n");
+ log(" -onehot ignore|pmux|shiftx\n");
+ log(" select strategy for one-hot encoded control signals\n");
+ log(" default: pmux\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ int min_density = 50;
+ int min_choices = 3;
+ bool allow_onehot = false;
+ bool optimize_onehot = true;
+ bool verbose = false;
+ bool verbose_onehot = false;
+
+ log_header(design, "Executing PMUX2SHIFTX pass.\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-min_density" && argidx+1 < args.size()) {
+ min_density = atoi(args[++argidx].c_str());
+ continue;
+ }
+ if (args[argidx] == "-min_choices" && argidx+1 < args.size()) {
+ min_choices = atoi(args[++argidx].c_str());
+ continue;
+ }
+ if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "ignore") {
+ argidx++;
+ allow_onehot = false;
+ optimize_onehot = false;
+ continue;
+ }
+ if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "pmux") {
+ argidx++;
+ allow_onehot = false;
+ optimize_onehot = true;
+ continue;
+ }
+ if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "shiftx") {
+ argidx++;
+ allow_onehot = true;
+ optimize_onehot = false;
+ continue;
+ }
+ if (args[argidx] == "-v") {
+ verbose = true;
+ continue;
+ }
+ if (args[argidx] == "-vv") {
+ verbose = true;
+ verbose_onehot = true;
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules())
+ {
+ SigMap sigmap(module);
+ OnehotDatabase onehot_db(module, sigmap);
+ onehot_db.verbose = verbose_onehot;
+
+ dict<SigBit, pair<SigSpec, Const>> eqdb;
+
+ for (auto cell : module->cells())
+ {
+ if (cell->type == "$eq")
+ {
+ dict<SigBit, State> bits;
+
+ SigSpec A = sigmap(cell->getPort("\\A"));
+ SigSpec B = sigmap(cell->getPort("\\B"));
+
+ int a_width = cell->getParam("\\A_WIDTH").as_int();
+ int b_width = cell->getParam("\\B_WIDTH").as_int();
+
+ if (a_width < b_width) {
+ bool a_signed = cell->getParam("\\A_SIGNED").as_int();
+ A.extend_u0(b_width, a_signed);
+ }
+
+ if (b_width < a_width) {
+ bool b_signed = cell->getParam("\\B_SIGNED").as_int();
+ B.extend_u0(a_width, b_signed);
+ }
+
+ for (int i = 0; i < GetSize(A); i++) {
+ SigBit a_bit = A[i], b_bit = B[i];
+ if (b_bit.wire && !a_bit.wire) {
+ std::swap(a_bit, b_bit);
+ }
+ if (!a_bit.wire || b_bit.wire)
+ goto next_cell;
+ if (bits.count(a_bit))
+ goto next_cell;
+ bits[a_bit] = b_bit.data;
+ }
+
+ if (GetSize(bits) > 20)
+ goto next_cell;
+
+ bits.sort();
+ pair<SigSpec, Const> entry;
+
+ for (auto it : bits) {
+ entry.first.append_bit(it.first);
+ entry.second.bits.push_back(it.second);
+ }
+
+ eqdb[sigmap(cell->getPort("\\Y")[0])] = entry;
+ goto next_cell;
+ }
+
+ if (cell->type == "$logic_not")
+ {
+ dict<SigBit, State> bits;
+
+ SigSpec A = sigmap(cell->getPort("\\A"));
+
+ for (int i = 0; i < GetSize(A); i++)
+ bits[A[i]] = State::S0;
+
+ bits.sort();
+ pair<SigSpec, Const> entry;
+
+ for (auto it : bits) {
+ entry.first.append_bit(it.first);
+ entry.second.bits.push_back(it.second);
+ }
+
+ eqdb[sigmap(cell->getPort("\\Y")[0])] = entry;
+ goto next_cell;
+ }
+ next_cell:;
+ }
+
+ for (auto cell : module->selected_cells())
+ {
+ if (cell->type != "$pmux")
+ continue;
+
+ string src = cell->get_src_attribute();
+ int width = cell->getParam("\\WIDTH").as_int();
+ int width_bits = ceil_log2(width);
+ int extwidth = width;
+
+ while (extwidth & (extwidth-1))
+ extwidth++;
+
+ dict<SigSpec, pool<int>> seldb;
+
+ SigSpec B = cell->getPort("\\B");
+ SigSpec S = sigmap(cell->getPort("\\S"));
+ for (int i = 0; i < GetSize(S); i++)
+ {
+ if (!eqdb.count(S[i]))
+ continue;
+
+ auto &entry = eqdb.at(S[i]);
+ seldb[entry.first].insert(i);
+ }
+
+ if (seldb.empty())
+ continue;
+
+ bool printed_pmux_header = false;
+
+ if (verbose) {
+ printed_pmux_header = true;
+ log("Inspecting $pmux cell %s/%s.\n", log_id(module), log_id(cell));
+ log(" data width: %d (next power-of-2 = %d, log2 = %d)\n", width, extwidth, width_bits);
+ }
+
+ SigSpec updated_S = cell->getPort("\\S");
+ SigSpec updated_B = cell->getPort("\\B");
+
+ while (!seldb.empty())
+ {
+ // pick the largest entry in seldb
+ SigSpec sig = seldb.begin()->first;
+ for (auto &it : seldb) {
+ if (GetSize(sig) < GetSize(it.first))
+ sig = it.first;
+ else if (GetSize(seldb.at(sig)) < GetSize(it.second))
+ sig = it.first;
+ }
+
+ // find the relevant choices
+ bool is_onehot = GetSize(sig) > 2;
+ dict<Const, int> choices;
+ for (int i : seldb.at(sig)) {
+ Const val = eqdb.at(S[i]).second;
+ int onebits = 0;
+ for (auto b : val.bits)
+ if (b == State::S1)
+ onebits++;
+ if (onebits > 1)
+ is_onehot = false;
+ choices[val] = i;
+ }
+
+ // TBD: also find choices that are using signals that are subsets of the bits in "sig"
+
+ if (!verbose)
+ {
+ if (is_onehot && !allow_onehot && !optimize_onehot) {
+ seldb.erase(sig);
+ continue;
+ }
+
+ if (GetSize(choices) < min_choices) {
+ seldb.erase(sig);
+ continue;
+ }
+ }
+
+ if (!printed_pmux_header) {
+ printed_pmux_header = true;
+ log("Inspecting $pmux cell %s/%s.\n", log_id(module), log_id(cell));
+ log(" data width: %d (next power-of-2 = %d, log2 = %d)\n", width, extwidth, width_bits);
+ }
+
+ log(" checking ctrl signal %s\n", log_signal(sig));
+
+ auto print_choices = [&]() {
+ log(" table of choices:\n");
+ for (auto &it : choices)
+ log(" %3d: %s: %s\n", it.second, log_signal(it.first),
+ log_signal(B.extract(it.second*width, width)));
+ };
+
+ if (verbose)
+ {
+ if (is_onehot && !allow_onehot && !optimize_onehot) {
+ print_choices();
+ log(" ignoring one-hot encoding.\n");
+ seldb.erase(sig);
+ continue;
+ }
+
+ if (GetSize(choices) < min_choices) {
+ print_choices();
+ log(" insufficient choices.\n");
+ seldb.erase(sig);
+ continue;
+ }
+ }
+
+ if (is_onehot && optimize_onehot)
+ {
+ print_choices();
+ if (!onehot_db.query(sig))
+ {
+ log(" failed to detect onehot driver. do not optimize.\n");
+ }
+ else
+ {
+ log(" optimizing one-hot encoding.\n");
+ for (auto &it : choices)
+ {
+ const Const &val = it.first;
+ int index = -1;
+
+ for (int i = 0; i < GetSize(val); i++)
+ if (val[i] == State::S1) {
+ log_assert(index < 0);
+ index = i;
+ }
+
+ if (index < 0) {
+ log(" %3d: zero encoding.\n", it.second);
+ continue;
+ }
+
+ SigBit new_ctrl = sig[index];
+ log(" %3d: new crtl signal is %s.\n", it.second, log_signal(new_ctrl));
+ updated_S[it.second] = new_ctrl;
+ }
+ }
+ seldb.erase(sig);
+ continue;
+ }
+
+ // find the best permutation
+ vector<int> perm_new_from_old(GetSize(sig));
+ Const perm_xormask(State::S0, GetSize(sig));
+ {
+ vector<int> values(GetSize(choices));
+ vector<bool> used_src_columns(GetSize(sig));
+ vector<vector<bool>> columns(GetSize(sig), vector<bool>(GetSize(values)));
+
+ for (int i = 0; i < GetSize(choices); i++) {
+ Const val = choices.element(i)->first;
+ for (int k = 0; k < GetSize(val); k++)
+ if (val[k] == State::S1)
+ columns[k][i] = true;
+ }
+
+ for (int dst_col = GetSize(sig)-1; dst_col >= 0; dst_col--)
+ {
+ int best_src_col = -1;
+ bool best_inv = false;
+ int best_maxval = 0;
+ int best_delta = 0;
+
+ // find best src column for this dst column
+ for (int src_col = 0; src_col < GetSize(sig); src_col++)
+ {
+ if (used_src_columns[src_col])
+ continue;
+
+ int this_maxval = 0;
+ int this_minval = 1 << 30;
+
+ int this_inv_maxval = 0;
+ int this_inv_minval = 1 << 30;
+
+ for (int i = 0; i < GetSize(values); i++)
+ {
+ int val = values[i];
+ int inv_val = val;
+
+ if (columns[src_col][i])
+ val |= 1 << dst_col;
+ else
+ inv_val |= 1 << dst_col;
+
+ this_maxval = std::max(this_maxval, val);
+ this_minval = std::min(this_minval, val);
+
+ this_inv_maxval = std::max(this_inv_maxval, inv_val);
+ this_inv_minval = std::min(this_inv_minval, inv_val);
+ }
+
+ int this_delta = this_maxval - this_minval;
+ int this_inv_delta = this_maxval - this_minval;
+ bool this_inv = false;
+
+ if (this_delta != this_inv_delta)
+ this_inv = this_inv_delta < this_delta;
+ else if (this_maxval != this_inv_maxval)
+ this_inv = this_inv_maxval < this_maxval;
+
+ if (this_inv) {
+ this_delta = this_inv_delta;
+ this_maxval = this_inv_maxval;
+ this_minval = this_inv_minval;
+ }
+
+ bool this_is_better = false;
+
+ if (best_src_col < 0)
+ this_is_better = true;
+ else if (this_delta != best_delta)
+ this_is_better = this_delta < best_delta;
+ else if (this_maxval != best_maxval)
+ this_is_better = this_maxval < best_maxval;
+ else
+ this_is_better = sig[best_src_col] < sig[src_col];
+
+ if (this_is_better) {
+ best_src_col = src_col;
+ best_inv = this_inv;
+ best_maxval = this_maxval;
+ best_delta = this_delta;
+ }
+ }
+
+ used_src_columns[best_src_col] = true;
+ perm_new_from_old[dst_col] = best_src_col;
+ perm_xormask[dst_col] = best_inv ? State::S1 : State::S0;
+ }
+ }
+
+ // permutated sig
+ SigSpec perm_sig(State::S0, GetSize(sig));
+ for (int i = 0; i < GetSize(sig); i++)
+ perm_sig[i] = sig[perm_new_from_old[i]];
+
+ log(" best permutation: %s\n", log_signal(perm_sig));
+ log(" best xor mask: %s\n", log_signal(perm_xormask));
+
+ // permutated choices
+ int min_choice = 1 << 30;
+ int max_choice = -1;
+ dict<Const, int> perm_choices;
+
+ for (auto &it : choices)
+ {
+ Const &old_c = it.first;
+ Const new_c(State::S0, GetSize(old_c));
+
+ for (int i = 0; i < GetSize(old_c); i++)
+ new_c[i] = old_c[perm_new_from_old[i]];
+
+ Const new_c_before_xor = new_c;
+ new_c = const_xor(new_c, perm_xormask, false, false, GetSize(new_c));
+
+ perm_choices[new_c] = it.second;
+
+ min_choice = std::min(min_choice, new_c.as_int());
+ max_choice = std::max(max_choice, new_c.as_int());
+
+ log(" %3d: %s -> %s -> %s: %s\n", it.second, log_signal(old_c), log_signal(new_c_before_xor),
+ log_signal(new_c), log_signal(B.extract(it.second*width, width)));
+ }
+
+ int range_density = 100*GetSize(choices) / (max_choice-min_choice+1);
+ int absolute_density = 100*GetSize(choices) / (max_choice+1);
+
+ log(" choices: %d\n", GetSize(choices));
+ log(" min choice: %d\n", min_choice);
+ log(" max choice: %d\n", max_choice);
+ log(" range density: %d%%\n", range_density);
+ log(" absolute density: %d%%\n", absolute_density);
+
+ bool full_case = (min_choice == 0) && (max_choice == (1 << GetSize(sig))-1) && (max_choice+1 == GetSize(choices));
+ log(" full case: %s\n", full_case ? "true" : "false");
+
+ // check density percentages
+ Const offset(State::S0, GetSize(sig));
+ if (absolute_density < min_density && range_density >= min_density)
+ {
+ offset = Const(min_choice, GetSize(sig));
+ log(" offset: %s\n", log_signal(offset));
+
+ min_choice -= offset.as_int();
+ max_choice -= offset.as_int();
+
+ dict<Const, int> new_perm_choices;
+ for (auto &it : perm_choices)
+ new_perm_choices[const_sub(it.first, offset, false, false, GetSize(sig))] = it.second;
+ perm_choices.swap(new_perm_choices);
+ } else
+ if (absolute_density < min_density) {
+ log(" insufficient density.\n");
+ seldb.erase(sig);
+ continue;
+ }
+
+ // creat cmp signal
+ SigSpec cmp = perm_sig;
+ if (perm_xormask.as_bool())
+ cmp = module->Xor(NEW_ID, cmp, perm_xormask, false, src);
+ if (offset.as_bool())
+ cmp = module->Sub(NEW_ID, cmp, offset, false, src);
+
+ // create enable signal
+ SigBit en = State::S1;
+ if (!full_case) {
+ Const enable_mask(State::S0, max_choice+1);
+ for (auto &it : perm_choices)
+ enable_mask[it.first.as_int()] = State::S1;
+ en = module->addWire(NEW_ID);
+ module->addShift(NEW_ID, enable_mask, cmp, en, false, src);
+ }
+
+ // create data signal
+ SigSpec data(State::Sx, (max_choice+1)*extwidth);
+ for (auto &it : perm_choices) {
+ int position = it.first.as_int()*extwidth;
+ int data_index = it.second;
+ data.replace(position, B.extract(data_index*width, width));
+ updated_S[data_index] = State::S0;
+ updated_B.replace(data_index*width, SigSpec(State::Sx, width));
+ }
+
+ // create shiftx cell
+ SigSpec shifted_cmp = {cmp, SigSpec(State::S0, width_bits)};
+ SigSpec outsig = module->addWire(NEW_ID, width);
+ Cell *c = module->addShiftx(NEW_ID, data, shifted_cmp, outsig, false, src);
+ updated_S.append(en);
+ updated_B.append(outsig);
+ log(" created $shiftx cell %s.\n", log_id(c));
+
+ // remove this sig and continue with the next block
+ seldb.erase(sig);
+ }
+
+ // update $pmux cell
+ cell->setPort("\\S", updated_S);
+ cell->setPort("\\B", updated_B);
+ cell->setParam("\\S_WIDTH", GetSize(updated_S));
+ }
+ }
+ }
+} Pmux2ShiftxPass;
+
+struct OnehotPass : public Pass {
+ OnehotPass() : Pass("onehot", "optimize $eq cells for onehot signals") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" onehot [options] [selection]\n");
+ log("\n");
+ log("This pass optimizes $eq cells that compare one-hot signals against constants\n");
+ log("\n");
+ log(" -v, -vv\n");
+ log(" verbose output\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ bool verbose = false;
+ bool verbose_onehot = false;
+
+ log_header(design, "Executing ONEHOT pass.\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-v") {
+ verbose = true;
+ continue;
+ }
+ if (args[argidx] == "-vv") {
+ verbose = true;
+ verbose_onehot = true;
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules())
+ {
+ SigMap sigmap(module);
+ OnehotDatabase onehot_db(module, sigmap);
+ onehot_db.verbose = verbose_onehot;
+
+ for (auto cell : module->selected_cells())
+ {
+ if (cell->type != "$eq")
+ continue;
+
+ SigSpec A = sigmap(cell->getPort("\\A"));
+ SigSpec B = sigmap(cell->getPort("\\B"));
+
+ int a_width = cell->getParam("\\A_WIDTH").as_int();
+ int b_width = cell->getParam("\\B_WIDTH").as_int();
+
+ if (a_width < b_width) {
+ bool a_signed = cell->getParam("\\A_SIGNED").as_int();
+ A.extend_u0(b_width, a_signed);
+ }
+
+ if (b_width < a_width) {
+ bool b_signed = cell->getParam("\\B_SIGNED").as_int();
+ B.extend_u0(a_width, b_signed);
+ }
+
+ if (A.is_fully_const())
+ std::swap(A, B);
+
+ if (!B.is_fully_const())
+ continue;
+
+ if (verbose)
+ log("Checking $eq(%s, %s) cell %s/%s.\n", log_signal(A), log_signal(B), log_id(module), log_id(cell));
+
+ if (!onehot_db.query(A)) {
+ if (verbose)
+ log(" onehot driver test on %s failed.\n", log_signal(A));
+ continue;
+ }
+
+ int index = -1;
+ bool not_onehot = false;
+
+ for (int i = 0; i < GetSize(B); i++) {
+ if (B[i] != State::S1)
+ continue;
+ if (index >= 0)
+ not_onehot = true;
+ index = i;
+ }
+
+ if (index < 0) {
+ if (verbose)
+ log(" not optimizing the zero pattern.\n");
+ continue;
+ }
+
+ SigSpec Y = cell->getPort("\\Y");
+
+ if (not_onehot)
+ {
+ if (verbose)
+ log(" replacing with constant 0 driver.\n");
+ else
+ log("Replacing one-hot $eq(%s, %s) cell %s/%s with constant 0 driver.\n", log_signal(A), log_signal(B), log_id(module), log_id(cell));
+ module->connect(Y, SigSpec(1, GetSize(Y)));
+ }
+ else
+ {
+ SigSpec sig = A[index];
+ if (verbose)
+ log(" replacing with signal %s.\n", log_signal(sig));
+ else
+ log("Replacing one-hot $eq(%s, %s) cell %s/%s with signal %s.\n",log_signal(A), log_signal(B), log_id(module), log_id(cell), log_signal(sig));
+ sig.extend_u0(GetSize(Y));
+ module->connect(Y, sig);
+ }
+
+ module->remove(cell);
+ }
+ }
+ }
+} OnehotPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/shregmap.cc b/passes/techmap/shregmap.cc
index f20863ba0..a541b33be 100644
--- a/passes/techmap/shregmap.cc
+++ b/passes/techmap/shregmap.cc
@@ -26,7 +26,9 @@ PRIVATE_NAMESPACE_BEGIN
struct ShregmapTech
{
virtual ~ShregmapTech() { }
- virtual bool analyze(vector<int> &taps) = 0;
+ virtual void init(const Module * /*module*/, const SigMap &/*sigmap*/) {}
+ virtual void non_chain_user(const SigBit &/*bit*/, const Cell* /*cell*/, IdString /*port*/) {}
+ virtual bool analyze(vector<int> &taps, const vector<SigBit> &qbits) = 0;
virtual bool fixup(Cell *cell, dict<int, SigBit> &taps) = 0;
};
@@ -54,7 +56,7 @@ struct ShregmapOptions
struct ShregmapTechGreenpak4 : ShregmapTech
{
- bool analyze(vector<int> &taps)
+ bool analyze(vector<int> &taps, const vector<SigBit> &/*qbits*/)
{
if (GetSize(taps) > 2 && taps[0] == 0 && taps[2] < 17) {
taps.clear();
@@ -91,6 +93,145 @@ struct ShregmapTechGreenpak4 : ShregmapTech
}
};
+struct ShregmapTechXilinx7 : ShregmapTech
+{
+ dict<SigBit, std::tuple<Cell*,int,int>> sigbit_to_shiftx_offset;
+ const ShregmapOptions &opts;
+
+ ShregmapTechXilinx7(const ShregmapOptions &opts) : opts(opts) {}
+
+ virtual void init(const Module* module, const SigMap &sigmap) override
+ {
+ for (const auto &i : module->cells_) {
+ auto cell = i.second;
+ if (cell->type == "$shiftx") {
+ if (cell->getParam("\\Y_WIDTH") != 1) continue;
+ int j = 0;
+ for (auto bit : sigmap(cell->getPort("\\A")))
+ sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, j++, 0);
+ log_assert(j == cell->getParam("\\A_WIDTH").as_int());
+ }
+ else if (cell->type == "$mux") {
+ int j = 0;
+ for (auto bit : sigmap(cell->getPort("\\A")))
+ sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 0, j++);
+ j = 0;
+ for (auto bit : sigmap(cell->getPort("\\B")))
+ sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 1, j++);
+ }
+ }
+ }
+
+ virtual void non_chain_user(const SigBit &bit, const Cell *cell, IdString port) override
+ {
+ auto it = sigbit_to_shiftx_offset.find(bit);
+ if (it == sigbit_to_shiftx_offset.end())
+ return;
+ if (cell) {
+ if (cell->type == "$shiftx" && port == "\\A")
+ return;
+ if (cell->type == "$mux" && (port == "\\A" || port == "\\B"))
+ return;
+ }
+ sigbit_to_shiftx_offset.erase(it);
+ }
+
+ virtual bool analyze(vector<int> &taps, const vector<SigBit> &qbits) override
+ {
+ if (GetSize(taps) == 1)
+ return taps[0] >= opts.minlen-1 && sigbit_to_shiftx_offset.count(qbits[0]);
+
+ if (taps.back() < opts.minlen-1)
+ return false;
+
+ Cell *shiftx = nullptr;
+ int group = 0;
+ for (int i = 0; i < GetSize(taps); ++i) {
+ auto it = sigbit_to_shiftx_offset.find(qbits[i]);
+ if (it == sigbit_to_shiftx_offset.end())
+ return false;
+
+ // Check taps are sequential
+ if (i != taps[i])
+ return false;
+ // Check taps are not connected to a shift register,
+ // or sequential to the same shift register
+ if (i == 0) {
+ int offset;
+ std::tie(shiftx,offset,group) = it->second;
+ if (offset != i)
+ return false;
+ }
+ else {
+ Cell *shiftx_ = std::get<0>(it->second);
+ if (shiftx_ != shiftx)
+ return false;
+ int offset = std::get<1>(it->second);
+ if (offset != i)
+ return false;
+ int group_ = std::get<2>(it->second);
+ if (group_ != group)
+ return false;
+ }
+ }
+ log_assert(shiftx);
+
+ // Only map if $shiftx exclusively covers the shift register
+ if (shiftx->type == "$shiftx") {
+ if (GetSize(taps) != shiftx->getParam("\\A_WIDTH").as_int())
+ return false;
+ }
+ else if (shiftx->type == "$mux") {
+ if (GetSize(taps) != 2)
+ return false;
+ }
+ else log_abort();
+
+ return true;
+ }
+
+ virtual bool fixup(Cell *cell, dict<int, SigBit> &taps) override
+ {
+ const auto &tap = *taps.begin();
+ auto bit = tap.second;
+
+ auto it = sigbit_to_shiftx_offset.find(bit);
+ log_assert(it != sigbit_to_shiftx_offset.end());
+
+ auto newcell = cell->module->addCell(NEW_ID, "$__XILINX_SHREG_");
+ newcell->set_src_attribute(cell->get_src_attribute());
+ newcell->setParam("\\DEPTH", cell->getParam("\\DEPTH"));
+ newcell->setParam("\\INIT", cell->getParam("\\INIT"));
+ newcell->setParam("\\CLKPOL", cell->getParam("\\CLKPOL"));
+ newcell->setParam("\\ENPOL", cell->getParam("\\ENPOL"));
+
+ newcell->setPort("\\C", cell->getPort("\\C"));
+ newcell->setPort("\\D", cell->getPort("\\D"));
+ if (cell->hasPort("\\E"))
+ newcell->setPort("\\E", cell->getPort("\\E"));
+
+ Cell* shiftx = std::get<0>(it->second);
+ RTLIL::SigSpec l_wire, q_wire;
+ if (shiftx->type == "$shiftx") {
+ l_wire = shiftx->getPort("\\B");
+ q_wire = shiftx->getPort("\\Y");
+ shiftx->setPort("\\Y", cell->module->addWire(NEW_ID));
+ }
+ else if (shiftx->type == "$mux") {
+ l_wire = shiftx->getPort("\\S");
+ q_wire = shiftx->getPort("\\Y");
+ shiftx->setPort("\\Y", cell->module->addWire(NEW_ID));
+ }
+ else log_abort();
+
+ newcell->setPort("\\Q", q_wire);
+ newcell->setPort("\\L", l_wire);
+
+ return false;
+ }
+};
+
+
struct ShregmapWorker
{
Module *module;
@@ -113,8 +254,10 @@ struct ShregmapWorker
for (auto wire : module->wires())
{
if (wire->port_output || wire->get_bool_attribute("\\keep")) {
- for (auto bit : sigmap(wire))
+ for (auto bit : sigmap(wire)) {
sigbit_with_non_chain_users.insert(bit);
+ if (opts.tech) opts.tech->non_chain_user(bit, nullptr, {});
+ }
}
if (wire->attributes.count("\\init")) {
@@ -152,8 +295,10 @@ struct ShregmapWorker
for (auto conn : cell->connections())
if (cell->input(conn.first))
- for (auto bit : sigmap(conn.second))
+ for (auto bit : sigmap(conn.second)) {
sigbit_with_non_chain_users.insert(bit);
+ if (opts.tech) opts.tech->non_chain_user(bit, cell, conn.first);
+ }
}
}
@@ -258,7 +403,7 @@ struct ShregmapWorker
if (taps.empty() || taps.back() < depth-1)
taps.push_back(depth-1);
- if (opts.tech->analyze(taps))
+ if (opts.tech->analyze(taps, qbits))
break;
taps.pop_back();
@@ -377,6 +522,9 @@ struct ShregmapWorker
ShregmapWorker(Module *module, const ShregmapOptions &opts) :
module(module), sigmap(module), opts(opts), dff_count(0), shreg_count(0)
{
+ if (opts.tech)
+ opts.tech->init(module, sigmap);
+
make_sigbit_chain_next_prev();
find_chain_start_cells();
@@ -501,6 +649,12 @@ struct ShregmapPass : public Pass {
clkpol = "pos";
opts.zinit = true;
opts.tech = new ShregmapTechGreenpak4;
+ }
+ else if (tech == "xilinx") {
+ opts.init = true;
+ opts.params = true;
+ enpol = "any_or_none";
+ opts.tech = new ShregmapTechXilinx7(opts);
} else {
argidx--;
break;
diff --git a/techlibs/xilinx/cells_map.v b/techlibs/xilinx/cells_map.v
index d5801c0fc..704ab21b1 100644
--- a/techlibs/xilinx/cells_map.v
+++ b/techlibs/xilinx/cells_map.v
@@ -17,4 +17,130 @@
*
*/
-// Empty for now
+module \$__SHREG_ (input C, input D, input E, output Q);
+ parameter DEPTH = 0;
+ parameter [DEPTH-1:0] INIT = 0;
+ parameter CLKPOL = 1;
+ parameter ENPOL = 2;
+
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH), .INIT(INIT), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) _TECHMAP_REPLACE_ (.C(C), .D(D), .L(DEPTH-1), .E(E), .Q(Q));
+endmodule
+
+module \$__XILINX_SHREG_ (input C, input D, input [31:0] L, input E, output Q, output SO);
+ parameter DEPTH = 0;
+ parameter [DEPTH-1:0] INIT = 0;
+ parameter CLKPOL = 1;
+ parameter ENPOL = 2;
+
+ // shregmap's INIT parameter shifts out LSB first;
+ // however Xilinx expects MSB first
+ function [DEPTH-1:0] brev;
+ input [DEPTH-1:0] din;
+ integer i;
+ begin
+ for (i = 0; i < DEPTH; i=i+1)
+ brev[i] = din[DEPTH-1-i];
+ end
+ endfunction
+ localparam [DEPTH-1:0] INIT_R = brev(INIT);
+
+ parameter _TECHMAP_CONSTMSK_L_ = 0;
+ parameter _TECHMAP_CONSTVAL_L_ = 0;
+
+ wire CE;
+ generate
+ if (ENPOL == 0)
+ assign CE = ~E;
+ else if (ENPOL == 1)
+ assign CE = E;
+ else
+ assign CE = 1'b1;
+ if (DEPTH == 1) begin
+ if (CLKPOL)
+ FDRE #(.INIT(INIT_R)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(CE), .R(1'b0));
+ else
+ FDRE_1 #(.INIT(INIT_R)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(CE), .R(1'b0));
+ end else
+ if (DEPTH <= 16) begin
+ SRL16E #(.INIT(INIT_R), .IS_CLK_INVERTED(~CLKPOL[0])) _TECHMAP_REPLACE_ (.A0(L[0]), .A1(L[1]), .A2(L[2]), .A3(L[3]), .CE(CE), .CLK(C), .D(D), .Q(Q));
+ end else
+ if (DEPTH > 17 && DEPTH <= 32) begin
+ SRLC32E #(.INIT(INIT_R), .IS_CLK_INVERTED(~CLKPOL[0])) _TECHMAP_REPLACE_ (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(Q));
+ end else
+ if (DEPTH > 33 && DEPTH <= 64) begin
+ wire T0, T1, T2;
+ SRLC32E #(.INIT(INIT_R[32-1:0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(T0), .Q31(T1));
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH-32), .INIT(INIT[DEPTH-32-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_1 (.C(C), .D(T1), .L(L), .E(E), .Q(T2));
+ if (&_TECHMAP_CONSTMSK_L_)
+ assign Q = T2;
+ else
+ MUXF7 fpga_mux_0 (.O(Q), .I0(T0), .I1(T2), .S(L[5]));
+ end else
+ if (DEPTH > 65 && DEPTH <= 96) begin
+ wire T0, T1, T2, T3, T4, T5, T6;
+ SRLC32E #(.INIT(INIT_R[32-1:0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(T0), .Q31(T1));
+ SRLC32E #(.INIT(INIT_R[64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3));
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH-64), .INIT(INIT[DEPTH-64-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_2 (.C(C), .D(T3), .L(L[4:0]), .E(E), .Q(T4));
+ if (&_TECHMAP_CONSTMSK_L_)
+ assign Q = T4;
+ else begin
+ MUXF7 fpga_mux_0 (.O(T5), .I0(T0), .I1(T2), .S(L[5]));
+ MUXF7 fpga_mux_1 (.O(T6), .I0(T4), .I1(1'b0 /* unused */), .S(L[5]));
+ MUXF8 fpga_mux_2 (.O(Q), .I0(T5), .I1(T6), .S(L[6]));
+ end
+ end else
+ if (DEPTH > 97 && DEPTH < 128) begin
+ wire T0, T1, T2, T3, T4, T5, T6, T7, T8;
+ SRLC32E #(.INIT(INIT_R[32-1:0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(T0), .Q31(T1));
+ SRLC32E #(.INIT(INIT_R[64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3));
+ SRLC32E #(.INIT(INIT_R[96-1:64]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_2 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T3), .Q(T4), .Q31(T5));
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH-96), .INIT(INIT[DEPTH-96-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_3 (.C(C), .D(T5), .L(L[4:0]), .E(E), .Q(T6));
+ if (&_TECHMAP_CONSTMSK_L_)
+ assign Q = T6;
+ else begin
+ MUXF7 fpga_mux_0 (.O(T7), .I0(T0), .I1(T2), .S(L[5]));
+ MUXF7 fpga_mux_1 (.O(T8), .I0(T4), .I1(T6), .S(L[5]));
+ MUXF8 fpga_mux_2 (.O(Q), .I0(T7), .I1(T8), .S(L[6]));
+ end
+ end
+ else if (DEPTH == 128) begin
+ wire T0, T1, T2, T3, T4, T5, T6;
+ SRLC32E #(.INIT(INIT_R[32-1:0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(T0), .Q31(T1));
+ SRLC32E #(.INIT(INIT_R[64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3));
+ SRLC32E #(.INIT(INIT_R[96-1:64]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_2 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T3), .Q(T4), .Q31(T5));
+ SRLC32E #(.INIT(INIT_R[128-1:96]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_3 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T5), .Q(T6), .Q31(SO));
+ if (&_TECHMAP_CONSTMSK_L_)
+ assign Q = T6;
+ else begin
+ wire T7, T8;
+ MUXF7 fpga_mux_0 (.O(T7), .I0(T0), .I1(T2), .S(L[5]));
+ MUXF7 fpga_mux_1 (.O(T8), .I0(T4), .I1(T6), .S(L[5]));
+ MUXF8 fpga_mux_2 (.O(Q), .I0(T7), .I1(T8), .S(L[6]));
+ end
+ end
+ else if (DEPTH <= 129 && ~&_TECHMAP_CONSTMSK_L_) begin
+ // Handle cases where fixed-length depth is
+ // just 1 over a convenient value
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH+1), .INIT({INIT,1'b0}), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) _TECHMAP_REPLACE_ (.C(C), .D(D), .L(L), .E(E), .Q(Q));
+ end
+ else begin
+ localparam lower_clog2 = $clog2((DEPTH+1)/2);
+ localparam lower_depth = 2 ** lower_clog2;
+ wire T0, T1, T2, T3;
+ if (&_TECHMAP_CONSTMSK_L_) begin
+ \$__XILINX_SHREG_ #(.DEPTH(lower_depth), .INIT(INIT[DEPTH-1:DEPTH-lower_depth]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_0 (.C(C), .D(D), .L(lower_depth-1), .E(E), .Q(T0));
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH-lower_depth), .INIT(INIT[DEPTH-lower_depth-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_1 (.C(C), .D(T0), .L(DEPTH-lower_depth-1), .E(E), .Q(Q), .SO(T3));
+ end
+ else begin
+ \$__XILINX_SHREG_ #(.DEPTH(lower_depth), .INIT(INIT[DEPTH-1:DEPTH-lower_depth]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_0 (.C(C), .D(D), .L(L[lower_clog2-1:0]), .E(E), .Q(T0), .SO(T1));
+ \$__XILINX_SHREG_ #(.DEPTH(DEPTH-lower_depth), .INIT(INIT[DEPTH-lower_depth-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_1 (.C(C), .D(T1), .L(L[lower_clog2-1:0]), .E(E), .Q(T2), .SO(T3));
+ assign Q = L[lower_clog2] ? T2 : T0;
+ end
+ if (DEPTH == 2 * lower_depth)
+ assign SO = T3;
+ end
+ endgenerate
+endmodule
+
+`ifndef SRL_ONLY
+`endif
diff --git a/techlibs/xilinx/cells_sim.v b/techlibs/xilinx/cells_sim.v
index 0c8f282a4..3a4540b83 100644
--- a/techlibs/xilinx/cells_sim.v
+++ b/techlibs/xilinx/cells_sim.v
@@ -308,3 +308,42 @@ module RAM128X1D (
wire clk = WCLK ^ IS_WCLK_INVERTED;
always @(posedge clk) if (WE) mem[A] <= D;
endmodule
+
+module SRL16E (
+ output Q,
+ input A0, A1, A2, A3, CE, CLK, D
+);
+ parameter [15:0] INIT = 16'h0000;
+ parameter [0:0] IS_CLK_INVERTED = 1'b0;
+
+ reg [15:0] r = INIT;
+ assign Q = r[{A3,A2,A1,A0}];
+ generate
+ if (IS_CLK_INVERTED) begin
+ always @(negedge CLK) if (CE) r <= { r[14:0], D };
+ end
+ else
+ always @(posedge CLK) if (CE) r <= { r[14:0], D };
+ endgenerate
+endmodule
+
+module SRLC32E (
+ output Q,
+ output Q31,
+ input [4:0] A,
+ input CE, CLK, D
+);
+ parameter [31:0] INIT = 32'h00000000;
+ parameter [0:0] IS_CLK_INVERTED = 1'b0;
+
+ reg [31:0] r = INIT;
+ assign Q31 = r[31];
+ assign Q = r[A];
+ generate
+ if (IS_CLK_INVERTED) begin
+ always @(negedge CLK) if (CE) r <= { r[30:0], D };
+ end
+ else
+ always @(posedge CLK) if (CE) r <= { r[30:0], D };
+ endgenerate
+endmodule
diff --git a/techlibs/xilinx/cells_xtra.sh b/techlibs/xilinx/cells_xtra.sh
index 8b06c3155..8e39b440d 100644
--- a/techlibs/xilinx/cells_xtra.sh
+++ b/techlibs/xilinx/cells_xtra.sh
@@ -135,8 +135,8 @@ function xtract_cell_decl()
xtract_cell_decl ROM256X1
xtract_cell_decl ROM32X1
xtract_cell_decl ROM64X1
- xtract_cell_decl SRL16E
- xtract_cell_decl SRLC32E
+ #xtract_cell_decl SRL16E
+ #xtract_cell_decl SRLC32E
xtract_cell_decl STARTUPE2 "(* keep *)"
xtract_cell_decl USR_ACCESSE2
xtract_cell_decl XADC
diff --git a/techlibs/xilinx/cells_xtra.v b/techlibs/xilinx/cells_xtra.v
index 4fb6798be..fbcc74682 100644
--- a/techlibs/xilinx/cells_xtra.v
+++ b/techlibs/xilinx/cells_xtra.v
@@ -3809,22 +3809,6 @@ module ROM64X1 (...);
input A0, A1, A2, A3, A4, A5;
endmodule
-module SRL16E (...);
- parameter [15:0] INIT = 16'h0000;
- parameter [0:0] IS_CLK_INVERTED = 1'b0;
- output Q;
- input A0, A1, A2, A3, CE, CLK, D;
-endmodule
-
-module SRLC32E (...);
- parameter [31:0] INIT = 32'h00000000;
- parameter [0:0] IS_CLK_INVERTED = 1'b0;
- output Q;
- output Q31;
- input [4:0] A;
- input CE, CLK, D;
-endmodule
-
(* keep *)
module STARTUPE2 (...);
parameter PROG_USR = "FALSE";
diff --git a/techlibs/xilinx/synth_xilinx.cc b/techlibs/xilinx/synth_xilinx.cc
index da6c0a4b2..a9e50329c 100644
--- a/techlibs/xilinx/synth_xilinx.cc
+++ b/techlibs/xilinx/synth_xilinx.cc
@@ -64,10 +64,13 @@ struct SynthXilinxPass : public Pass
log(" (this feature is experimental and incomplete)\n");
log("\n");
log(" -nobram\n");
- log(" disable infering of block rams\n");
+ log(" disable inference of block rams\n");
log("\n");
log(" -nodram\n");
- log(" disable infering of distributed rams\n");
+ log(" disable inference of distributed rams\n");
+ log("\n");
+ log(" -nosrl\n");
+ log(" disable inference of shift registers\n");
log("\n");
log(" -run <from_label>:<to_label>\n");
log(" only run the commands between the labels (see below). an empty\n");
@@ -109,6 +112,10 @@ struct SynthXilinxPass : public Pass
log(" memory_map\n");
log(" dffsr2dff\n");
log(" dff2dffe\n");
+ log(" simplemap t:$dff t:$dffe (without '-nosrl' only)\n");
+ log(" pmux2shiftx (without '-nosrl' only)\n");
+ log(" opt_expr -mux_undef (without '-nosrl' only)\n");
+ log(" shregmap -tech xilinx -minlen 3 (without '-nosrl' only)\n");
log(" opt -full\n");
log(" techmap -map +/techmap.v -map +/xilinx/arith_map.v\n");
log(" opt -fast\n");
@@ -121,7 +128,8 @@ struct SynthXilinxPass : public Pass
log(" techmap -map +/techmap.v -map +/xilinx/ff_map.v t:$_DFF_?N?\n");
log(" abc -luts 2:2,3,6:5,10,20 [-dff]\n");
log(" clean\n");
- log(" techmap -map +/xilinx/lut_map.v -map +/xilinx/ff_map.v");
+ log(" shregmap -minlen 3 -init -params -enpol any_or_none (without '-nosrl' only)\n");
+ log(" techmap -map +/xilinx/lut_map.v -map +/xilinx/ff_map.v -map +/xilinx/cells_map.v");
log(" dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT \\\n");
log(" -ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT\n");
log("\n");
@@ -148,6 +156,7 @@ struct SynthXilinxPass : public Pass
bool vpr = false;
bool nobram = false;
bool nodram = false;
+ bool nosrl = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
@@ -192,6 +201,10 @@ struct SynthXilinxPass : public Pass
nodram = true;
continue;
}
+ if (args[argidx] == "-nosrl") {
+ nosrl = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
@@ -250,10 +263,25 @@ struct SynthXilinxPass : public Pass
if (check_label(active, run_from, run_to, "fine"))
{
- Pass::call(design, "opt -fast -full");
+ Pass::call(design, "opt -fast");
Pass::call(design, "memory_map");
Pass::call(design, "dffsr2dff");
Pass::call(design, "dff2dffe");
+
+ if (!nosrl) {
+ // shregmap operates on bit-level flops, not word-level,
+ // so break those down here
+ Pass::call(design, "simplemap t:$dff t:$dffe");
+ // shregmap -tech xilinx can cope with $shiftx and $mux
+ // cells for identifiying variable-length shift registers,
+ // so attempt to convert $pmux-es to the former
+ Pass::call(design, "pmux2shiftx");
+ // pmux2shiftx can leave behind a $pmux with a single entry
+ // -- need this to clean that up
+ Pass::call(design, "opt_expr -mux_undef");
+ Pass::call(design, "shregmap -tech xilinx -minlen 3");
+ }
+
Pass::call(design, "opt -full");
if (vpr) {
@@ -277,7 +305,9 @@ struct SynthXilinxPass : public Pass
Pass::call(design, "techmap -map +/techmap.v -map +/xilinx/ff_map.v t:$_DFF_?N?");
Pass::call(design, "abc -luts 2:2,3,6:5,10,20" + string(retime ? " -dff" : ""));
Pass::call(design, "clean");
- Pass::call(design, "techmap -map +/xilinx/lut_map.v -map +/xilinx/ff_map.v");
+ if (!nosrl)
+ Pass::call(design, "shregmap -minlen 3 -init -params -enpol any_or_none");
+ Pass::call(design, "techmap -map +/xilinx/lut_map.v -map +/xilinx/ff_map.v -map +/xilinx/cells_map.v");
Pass::call(design, "dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT "
"-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT");
}
diff --git a/tests/various/pmux2shiftx.v b/tests/various/pmux2shiftx.v
new file mode 100644
index 000000000..fec84187b
--- /dev/null
+++ b/tests/various/pmux2shiftx.v
@@ -0,0 +1,34 @@
+module pmux2shiftx_test (
+ input [2:0] S1,
+ input [5:0] S2,
+ input [1:0] S3,
+ input [9:0] A, B, C, D, D, E, F, G, H,
+ input [9:0] I, J, K, L, M, N, O, P, Q,
+ output reg [9:0] X
+);
+ always @* begin
+ case (S1)
+ 3'd 0: X = A;
+ 3'd 1: X = B;
+ 3'd 2: X = C;
+ 3'd 3: X = D;
+ 3'd 4: X = E;
+ 3'd 5: X = F;
+ 3'd 6: X = G;
+ 3'd 7: X = H;
+ endcase
+ case (S2)
+ 6'd 45: X = I;
+ 6'd 47: X = J;
+ 6'd 49: X = K;
+ 6'd 55: X = L;
+ 6'd 57: X = M;
+ 6'd 59: X = N;
+ endcase
+ case (S3)
+ 2'd 1: X = O;
+ 2'd 2: X = P;
+ 2'd 3: X = Q;
+ endcase
+ end
+endmodule
diff --git a/tests/various/pmux2shiftx.ys b/tests/various/pmux2shiftx.ys
new file mode 100644
index 000000000..6bb9626eb
--- /dev/null
+++ b/tests/various/pmux2shiftx.ys
@@ -0,0 +1,28 @@
+read_verilog pmux2shiftx.v
+prep
+design -save gold
+
+pmux2shiftx -min_density 70
+
+opt
+
+stat
+# show -width
+select -assert-count 1 t:$sub
+select -assert-count 2 t:$mux
+select -assert-count 2 t:$shift
+select -assert-count 3 t:$shiftx
+
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+sat -verify -prove-asserts -show-ports miter
+
+design -load gold
+stat
+
+design -load gate
+stat