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/clean_zerowidth.cc2
-rw-r--r--passes/cmds/glift.cc599
-rw-r--r--passes/cmds/select.cc21
-rw-r--r--passes/cmds/show.cc48
-rw-r--r--passes/cmds/sta.cc5
-rw-r--r--passes/cmds/stat.cc13
-rw-r--r--passes/fsm/fsmdata.h4
-rw-r--r--passes/hierarchy/hierarchy.cc26
-rw-r--r--passes/memory/Makefile.inc3
-rw-r--r--passes/memory/memlib.cc1101
-rw-r--r--passes/memory/memlib.h171
-rw-r--r--passes/memory/memlib.md505
-rw-r--r--passes/memory/memory.cc19
-rw-r--r--passes/memory/memory_bmux2rom.cc87
-rw-r--r--passes/memory/memory_bram.cc28
-rw-r--r--passes/memory/memory_dff.cc27
-rw-r--r--passes/memory/memory_libmap.cc2096
-rw-r--r--passes/memory/memory_share.cc16
-rw-r--r--passes/opt/Makefile.inc1
-rw-r--r--passes/opt/opt.cc1
-rw-r--r--passes/opt/opt_dff.cc212
-rw-r--r--passes/opt/opt_ffinv.cc265
-rw-r--r--passes/opt/opt_mem.cc156
-rw-r--r--passes/opt/opt_merge.cc22
-rw-r--r--passes/opt/opt_reduce.cc397
-rw-r--r--passes/opt/wreduce.cc10
-rw-r--r--passes/pmgen/ice40_dsp.pmg5
-rw-r--r--passes/proc/Makefile.inc1
-rw-r--r--passes/proc/proc.cc11
-rw-r--r--passes/proc/proc_rom.cc252
-rw-r--r--passes/sat/Makefile.inc2
-rw-r--r--passes/sat/clk2fflogic.cc45
-rw-r--r--passes/sat/fmcombine.cc3
-rw-r--r--passes/sat/qbfsat.cc2
-rw-r--r--passes/sat/sim.cc1607
-rw-r--r--passes/techmap/Makefile.inc2
-rw-r--r--passes/techmap/abc.cc353
-rw-r--r--passes/techmap/abc9.cc10
-rw-r--r--passes/techmap/abc9_ops.cc24
-rw-r--r--passes/techmap/bmuxmap.cc76
-rw-r--r--passes/techmap/demuxmap.cc80
-rw-r--r--passes/techmap/iopadmap.cc30
-rw-r--r--passes/techmap/simplemap.cc26
-rw-r--r--passes/techmap/tribuf.cc49
-rw-r--r--passes/tests/test_cell.cc46
46 files changed, 7977 insertions, 483 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc
index 917856767..16a38b511 100644
--- a/passes/cmds/Makefile.inc
+++ b/passes/cmds/Makefile.inc
@@ -18,6 +18,7 @@ OBJS += passes/cmds/setattr.o
OBJS += passes/cmds/copy.o
OBJS += passes/cmds/splice.o
OBJS += passes/cmds/scc.o
+OBJS += passes/cmds/glift.o
OBJS += passes/cmds/torder.o
OBJS += passes/cmds/logcmd.o
OBJS += passes/cmds/tee.o
diff --git a/passes/cmds/clean_zerowidth.cc b/passes/cmds/clean_zerowidth.cc
index 4e7c68093..bac6b1521 100644
--- a/passes/cmds/clean_zerowidth.cc
+++ b/passes/cmds/clean_zerowidth.cc
@@ -80,7 +80,7 @@ struct CleanZeroWidthPass : public Pass {
if (GetSize(cell->getPort(ID::Q)) == 0) {
module->remove(cell);
}
- } else if (cell->type == ID($pmux)) {
+ } else if (cell->type.in(ID($pmux), ID($bmux), ID($demux))) {
// Remove altogether if WIDTH is 0, replace with
// a connection if S_WIDTH is 0.
if (cell->getParam(ID::WIDTH).as_int() == 0) {
diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc
new file mode 100644
index 000000000..b398c3e04
--- /dev/null
+++ b/passes/cmds/glift.cc
@@ -0,0 +1,599 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 Alberto Gonzalez <boqwxp@airmail.cc>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+#include "kernel/utils.h"
+#include "kernel/log.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct GliftWorker {
+private:
+ bool is_top_module = false;
+ bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false;
+ bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false;
+ bool opt_instrumentmore = false;
+ std::vector<RTLIL::Wire *> new_taint_outputs;
+ std::vector<std::pair<RTLIL::SigSpec, RTLIL::IdString>> meta_mux_selects;
+ RTLIL::Module *module = nullptr;
+
+ const RTLIL::IdString cost_model_wire_name = ID(__glift_weight);
+ const RTLIL::IdString glift_attribute_name = ID(glift);
+
+
+ RTLIL::SigSpec get_corresponding_taint_signal(RTLIL::SigSpec sig) {
+ RTLIL::SigSpec ret;
+
+ //Get the connected wire for the cell port:
+ log_assert(sig.is_wire() || sig.is_fully_const());
+ log_assert(sig.is_wire() || sig.is_fully_const());
+
+ //Get a SigSpec for the corresponding taint signal for the cell port, creating one if necessary:
+ if (sig.is_wire()) {
+ RTLIL::Wire *w = module->wire(sig.as_wire()->name.str() + "_t");
+ if (w == nullptr) w = module->addWire(sig.as_wire()->name.str() + "_t", 1);
+ ret = w;
+ }
+ else if (sig.is_fully_const() && opt_taintconstants)
+ ret = RTLIL::State::S1;
+ else if (sig.is_fully_const())
+ ret = RTLIL::State::S0;
+ else
+ log_cmd_error("Cell port SigSpec has unexpected type.\n");
+
+ //Finally, if the cell port was a module input or output, make sure the corresponding taint signal is marked, too:
+ if(sig.is_wire() && sig.as_wire()->port_input)
+ ret.as_wire()->port_input = true;
+ if(sig.is_wire() && sig.as_wire()->port_output)
+ new_taint_outputs.push_back(ret.as_wire());
+
+ return ret;
+ }
+
+ void add_precise_GLIFT_logic(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) {
+ //AKA AN2_SH2 or OR2_SH2
+ bool is_and = cell->type.in(ID($_AND_), ID($_NAND_));
+ RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_1_1", port_a, false, cell->get_src_attribute());
+ RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_1_2", port_b, false, cell->get_src_attribute());
+ auto subexpr1 = module->And(cell->name.str() + "_t_1_3", is_and? port_a : n_port_a, port_b_taint, false, cell->get_src_attribute());
+ auto subexpr2 = module->And(cell->name.str() + "_t_1_4", is_and? port_b : n_port_b, port_a_taint, false, cell->get_src_attribute());
+ auto subexpr3 = module->And(cell->name.str() + "_t_1_5", port_a_taint, port_b_taint, false, cell->get_src_attribute());
+ auto subexpr4 = module->Or(cell->name.str() + "_t_1_6", subexpr1, subexpr2, false, cell->get_src_attribute());
+ module->addOr(cell->name.str() + "_t_1_7", subexpr4, subexpr3, port_y_taint, false, cell->get_src_attribute());
+ }
+
+ void add_imprecise_GLIFT_logic_1(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) {
+ //AKA AN2_SH3 or OR2_SH3
+ bool is_and = cell->type.in(ID($_AND_), ID($_NAND_));
+ RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_2_1", port_a, false, cell->get_src_attribute());
+ auto subexpr1 = module->And(cell->name.str() + "_t_2_2", is_and? port_b : n_port_a, is_and? port_a_taint : port_b_taint, false, cell->get_src_attribute());
+ module->addOr(cell->name.str() + "_t_2_3", is_and? port_b_taint : port_a_taint, subexpr1, port_y_taint, false, cell->get_src_attribute());
+ }
+
+ void add_imprecise_GLIFT_logic_2(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) {
+ //AKA AN2_SH4 or OR2_SH4
+ bool is_and = cell->type.in(ID($_AND_), ID($_NAND_));
+ RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_3_1", port_b, false, cell->get_src_attribute());
+ auto subexpr1 = module->And(cell->name.str() + "_t_3_2", is_and? port_a : n_port_b, is_and? port_b_taint : port_a_taint, false, cell->get_src_attribute());
+ module->addOr(cell->name.str() + "_t_3_3", is_and? port_a_taint : port_b_taint, subexpr1, port_y_taint, false, cell->get_src_attribute());
+ }
+
+ void add_imprecise_GLIFT_logic_3(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) {
+ //AKA AN2_SH5 or OR2_SH5 or XR2_SH2
+ module->addOr(cell->name.str() + "_t_4_1", port_a_taint, port_b_taint, port_y_taint, false, cell->get_src_attribute());
+ }
+
+ void add_imprecise_GLIFT_logic_4(RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_y_taint) {
+ module->connect(port_y_taint, port_a_taint);
+ }
+
+ void add_imprecise_GLIFT_logic_5(RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) {
+ module->connect(port_y_taint, port_b_taint);
+ }
+
+ void add_imprecise_GLIFT_logic_6(RTLIL::SigSpec &port_y_taint) {
+ module->connect(port_y_taint, RTLIL::Const(1, 1));
+ }
+
+ void add_imprecise_GLIFT_logic_7(RTLIL::SigSpec &port_y_taint) {
+ module->connect(port_y_taint, RTLIL::Const(0, 1));
+ }
+
+ void add_precise_GLIFT_mux(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_s, RTLIL::SigSpec &port_s_taint, RTLIL::SigSpec &port_y_taint) {
+ //S&At | ~S&Bt | ~A&B&St | A&~B&St | At&St | Bt&St
+ RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_4_1", port_a, false, cell->get_src_attribute());
+ RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_4_2", port_b, false, cell->get_src_attribute());
+ RTLIL::SigSpec n_port_s = module->LogicNot(cell->name.str() + "_t_4_3", port_s, false, cell->get_src_attribute());
+ auto subexpr1 = module->And(cell->name.str() + "_t_4_4", port_s, port_a_taint, false, cell->get_src_attribute());
+ auto subexpr2 = module->And(cell->name.str() + "_t_4_5", n_port_s, port_b_taint, false, cell->get_src_attribute());
+ auto subexpr3 = module->And(cell->name.str() + "_t_4_6", n_port_a, port_b, false, cell->get_src_attribute());
+ auto subexpr4 = module->And(cell->name.str() + "_t_4_7", subexpr3, port_s_taint, false, cell->get_src_attribute());
+ auto subexpr5 = module->And(cell->name.str() + "_t_4_8", port_a, n_port_b, false, cell->get_src_attribute());
+ auto subexpr6 = module->And(cell->name.str() + "_t_4_9", subexpr5, port_s_taint, false, cell->get_src_attribute());
+ auto subexpr7 = module->And(cell->name.str() + "_t_4_10", port_a_taint, port_s_taint, false, cell->get_src_attribute());
+ auto subexpr8 = module->And(cell->name.str() + "_t_4_11", port_b_taint, port_s_taint, false, cell->get_src_attribute());
+ auto subexpr9 = module->Or(cell->name.str() + "_t_4_12", subexpr1, subexpr2, false, cell->get_src_attribute());
+ auto subexpr10 = module->Or(cell->name.str() + "_t_4_13", subexpr4, subexpr6, false, cell->get_src_attribute());
+ auto subexpr11 = module->Or(cell->name.str() + "_t_4_14", subexpr7, subexpr8, false, cell->get_src_attribute());
+ auto subexpr12 = module->Or(cell->name.str() + "_t_4_15", subexpr9, subexpr10, false, cell->get_src_attribute());
+ module->addOr(cell->name.str() + "_t_4_16", subexpr11, subexpr12, port_y_taint, false, cell->get_src_attribute());
+ }
+
+ RTLIL::SigSpec score_metamux_select(const RTLIL::SigSpec &metamux_select, const RTLIL::IdString celltype) {
+ log_assert(metamux_select.is_wire());
+
+ if (opt_simplecostmodel) {
+ //The complex model is an area model, so a lower score should mean smaller.
+ //In this case, a nonzero hole metamux select value means less logic.
+ //Thus we should invert the ReduceOr over the metamux_select signal.
+ RTLIL::SigSpec pmux_select = module->ReduceOr(metamux_select.as_wire()->name.str() + "_nonzero", metamux_select);
+ return module->Pmux(NEW_ID, RTLIL::Const(1), RTLIL::Const(0), pmux_select, metamux_select.as_wire()->get_src_attribute());
+ } else {
+ auto select_width = metamux_select.as_wire()->width;
+
+ std::vector<RTLIL::Const> costs;
+ if (celltype == ID($_AND_) || celltype == ID($_OR_)) {
+ costs = {5, 2, 2, 1, 0, 0, 0, 0};
+ log_assert(select_width == 2 || select_width == 3);
+ log_assert(opt_instrumentmore || select_width == 2);
+ log_assert(!opt_instrumentmore || select_width == 3);
+ }
+ else if (celltype == ID($_XOR_) || celltype == ID($_XNOR_)) {
+ costs = {1, 0, 0, 0};
+ log_assert(select_width == 2);
+ }
+
+ std::vector<RTLIL::SigSpec> next_pmux_y_ports, pmux_y_ports(costs.begin(), costs.begin() + exp2(select_width));
+ for (auto i = 0; pmux_y_ports.size() > 1; ++i) {
+ for (auto j = 0; j+1 < GetSize(pmux_y_ports); j += 2) {
+ next_pmux_y_ports.emplace_back(module->Pmux(stringf("%s_mux_%d_%d", metamux_select.as_wire()->name.c_str(), i, j), pmux_y_ports[j], pmux_y_ports[j+1], metamux_select[GetSize(metamux_select) - 1 - i], metamux_select.as_wire()->get_src_attribute()));
+ }
+ if (GetSize(pmux_y_ports) % 2 == 1)
+ next_pmux_y_ports.push_back(pmux_y_ports[GetSize(pmux_y_ports) - 1]);
+ pmux_y_ports.swap(next_pmux_y_ports);
+ next_pmux_y_ports.clear();
+ }
+
+ log_assert(pmux_y_ports.size() == 1);
+ return pmux_y_ports[0];
+ }
+ }
+
+ void create_glift_logic() {
+ if (module->get_bool_attribute(glift_attribute_name))
+ return;
+
+ std::vector<RTLIL::SigSig> connections(module->connections());
+
+ for(auto &cell : module->cells().to_vector()) {
+ if (!cell->type.in({ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_MUX_), ID($_NMUX_), ID($_NOT_), ID($anyconst), ID($allconst), ID($assume), ID($assert)}) && module->design->module(cell->type) == nullptr) {
+ log_cmd_error("Unsupported cell type \"%s\" found. Run `techmap` first.\n", cell->type.c_str());
+ }
+ if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_))) {
+ const unsigned int A = 0, B = 1, Y = 2;
+ const unsigned int NUM_PORTS = 3;
+ RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)};
+ RTLIL::SigSpec port_taints[NUM_PORTS];
+
+ if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1)
+ log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n");
+ for (unsigned int i = 0; i < NUM_PORTS; ++i)
+ port_taints[i] = get_corresponding_taint_signal(ports[i]);
+
+ if (opt_create_precise_model)
+ add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], port_taints[Y]);
+ else if (opt_create_imprecise_model)
+ add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]);
+ else if (opt_create_instrumented_model) {
+ std::vector<RTLIL::SigSpec> taint_version;
+ int num_versions = opt_instrumentmore? 8 : 4;
+
+ for (auto i = 1; i <= num_versions; ++i)
+ taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1)));
+
+ for (auto i = 0; i < num_versions; ++i) {
+ switch(i) {
+ case 0: add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]);
+ break;
+ case 1: add_imprecise_GLIFT_logic_1(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]);
+ break;
+ case 2: add_imprecise_GLIFT_logic_2(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]);
+ break;
+ case 3: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]);
+ break;
+ case 4: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]);
+ break;
+ case 5: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]);
+ break;
+ case 6: add_imprecise_GLIFT_logic_6(taint_version[i]);
+ break;
+ case 7: add_imprecise_GLIFT_logic_7(taint_version[i]);
+ break;
+ default: log_assert(false);
+ }
+ }
+
+ auto select_width = log2(num_versions);
+ log_assert(exp2(select_width) == num_versions);
+ RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width));
+ meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type));
+ module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute()));
+
+ std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version);
+ for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) {
+ for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) {
+ next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i]));
+ }
+ if (GetSize(meta_mux_y_ports) % 2 == 1)
+ next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]);
+ meta_mux_y_ports.swap(next_meta_mux_y_ports);
+ next_meta_mux_y_ports.clear();
+ }
+ log_assert(meta_mux_y_ports.size() == 1);
+ module->connect(port_taints[Y], meta_mux_y_ports[0]);
+ }
+ else log_cmd_error("This is a bug (1).\n");
+ }
+ else if (cell->type.in(ID($_XOR_), ID($_XNOR_))) {
+ const unsigned int A = 0, B = 1, Y = 2;
+ const unsigned int NUM_PORTS = 3;
+ RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)};
+ RTLIL::SigSpec port_taints[NUM_PORTS];
+
+ if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1)
+ log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n");
+ for (unsigned int i = 0; i < NUM_PORTS; ++i)
+ port_taints[i] = get_corresponding_taint_signal(ports[i]);
+
+ if (opt_create_precise_model || opt_create_imprecise_model)
+ add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]);
+ else if (opt_create_instrumented_model) {
+ std::vector<RTLIL::SigSpec> taint_version;
+ int num_versions = 4;
+ auto select_width = log2(num_versions);
+ log_assert(exp2(select_width) == num_versions);
+
+ for (auto i = 1; i <= num_versions; ++i)
+ taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1)));
+
+ for (auto i = 0; i < num_versions; ++i) {
+ switch(i) {
+ case 0: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]);
+ break;
+ case 1: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]);
+ break;
+ case 2: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]);
+ break;
+ case 3: add_imprecise_GLIFT_logic_6(taint_version[i]);
+ break;
+ default: log_assert(false);
+ }
+ }
+
+ RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width));
+ meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type));
+ module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute()));
+
+ std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version);
+ for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) {
+ for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) {
+ next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i]));
+ }
+ if (GetSize(meta_mux_y_ports) % 2 == 1)
+ next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]);
+ meta_mux_y_ports.swap(next_meta_mux_y_ports);
+ next_meta_mux_y_ports.clear();
+ }
+ log_assert(meta_mux_y_ports.size() == 1);
+ module->connect(port_taints[Y], meta_mux_y_ports[0]);
+ }
+ else log_cmd_error("This is a bug (2).\n");
+
+ }
+ else if (cell->type.in(ID($_MUX_), ID($_NMUX_))) {
+ const unsigned int A = 0, B = 1, S = 2, Y = 3;
+ const unsigned int NUM_PORTS = 4;
+ RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::S), cell->getPort(ID::Y)};
+ RTLIL::SigSpec port_taints[NUM_PORTS];
+
+ if (ports[A].size() != 1 || ports[B].size() != 1 || ports[S].size() != 1 || ports[Y].size() != 1)
+ log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n");
+ for (unsigned int i = 0; i < NUM_PORTS; ++i)
+ port_taints[i] = get_corresponding_taint_signal(ports[i]);
+
+ add_precise_GLIFT_mux(cell, ports[A], port_taints[A], ports[B], port_taints[B], ports[S], port_taints[S], port_taints[Y]);
+ }
+ else if (cell->type.in(ID($_NOT_))) {
+ const unsigned int A = 0, Y = 1;
+ const unsigned int NUM_PORTS = 2;
+ RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::Y)};
+ RTLIL::SigSpec port_taints[NUM_PORTS];
+
+ if (ports[A].size() != 1 || ports[Y].size() != 1)
+ log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n");
+ for (unsigned int i = 0; i < NUM_PORTS; ++i)
+ port_taints[i] = get_corresponding_taint_signal(ports[i]);
+
+ if (cell->type == ID($_NOT_)) {
+ module->connect(port_taints[Y], port_taints[A]);
+ }
+ else log_cmd_error("This is a bug (3).\n");
+ }
+ else if (module->design->module(cell->type) != nullptr) {
+ //User cell type
+ //This function is called on modules according to topological order, so we do not need to
+ //recurse to GLIFT model the child module. However, we need to augment the ports list
+ //with taint signals and connect the new ports to the corresponding taint signals.
+ RTLIL::Module *cell_module_def = module->design->module(cell->type);
+ dict<RTLIL::IdString, RTLIL::SigSpec> orig_ports = cell->connections();
+ log("Adding cell %s\n", cell_module_def->name.c_str());
+ for (auto &it : orig_ports) {
+ RTLIL::SigSpec port = it.second;
+ RTLIL::SigSpec port_taint = get_corresponding_taint_signal(port);
+
+ log_assert(port_taint.is_wire());
+ log_assert(std::find(cell_module_def->ports.begin(), cell_module_def->ports.end(), port_taint.as_wire()->name) != cell_module_def->ports.end());
+ cell->setPort(port_taint.as_wire()->name, port_taint);
+ }
+ }
+ else log_cmd_error("This is a bug (4).\n");
+ } //end foreach cell in cells
+
+ for (auto &conn : connections) {
+ RTLIL::SigSpec first = get_corresponding_taint_signal(conn.first);
+ RTLIL::SigSpec second = get_corresponding_taint_signal(conn.second);
+
+ module->connect(first, second);
+
+ if(conn.second.is_wire() && conn.second.as_wire()->port_input)
+ second.as_wire()->port_input = true;
+ if(conn.first.is_wire() && conn.first.as_wire()->port_output)
+ new_taint_outputs.push_back(first.as_wire());
+ } //end foreach conn in connections
+
+ //Create a rough model of area by summing the (potentially simplified) "weight" score of each meta-mux select:
+ if (!opt_nocostmodel) {
+ std::vector<RTLIL::SigSpec> meta_mux_select_sums;
+ std::vector<RTLIL::SigSpec> meta_mux_select_sums_buf;
+ for (auto &it : meta_mux_selects) {
+ meta_mux_select_sums.emplace_back(score_metamux_select(it.first, it.second));
+ }
+ for (unsigned int i = 0; meta_mux_select_sums.size() > 1; ) {
+ meta_mux_select_sums_buf.clear();
+ for (i = 0; i + 1 < meta_mux_select_sums.size(); i += 2) {
+ meta_mux_select_sums_buf.push_back(module->Add(meta_mux_select_sums[i].as_wire()->name.str() + "_add", meta_mux_select_sums[i], meta_mux_select_sums[i+1], false));
+ }
+ if (meta_mux_select_sums.size() % 2 == 1)
+ meta_mux_select_sums_buf.push_back(meta_mux_select_sums[meta_mux_select_sums.size()-1]);
+ meta_mux_select_sums.swap(meta_mux_select_sums_buf);
+ }
+ if (meta_mux_select_sums.size() > 0) {
+ meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\minimize");
+ meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\keep");
+ module->rename(meta_mux_select_sums[0].as_wire(), cost_model_wire_name);
+ }
+ }
+
+ //Mark new module outputs:
+ for (auto &port_name : module->ports) {
+ RTLIL::Wire *port = module->wire(port_name);
+ log_assert(port != nullptr);
+ if (is_top_module && port->port_output && !opt_keepoutputs)
+ port->port_output = false;
+ }
+ for (auto &output : new_taint_outputs)
+ output->port_output = true;
+ module->fixup_ports(); //we have some new taint signals in the module interface
+ module->set_bool_attribute(glift_attribute_name, true);
+ }
+
+public:
+ GliftWorker(RTLIL::Module *_module, bool _is_top_module, bool _opt_create_precise_model, bool _opt_create_imprecise_model, bool _opt_create_instrumented_model, bool _opt_taintconstants, bool _opt_keepoutputs, bool _opt_simplecostmodel, bool _opt_nocostmodel, bool _opt_instrumentmore) {
+ module = _module;
+ is_top_module = _is_top_module;
+ opt_create_precise_model = _opt_create_precise_model;
+ opt_create_imprecise_model = _opt_create_imprecise_model;
+ opt_create_instrumented_model = _opt_create_instrumented_model;
+ opt_taintconstants = _opt_taintconstants;
+ opt_keepoutputs = _opt_keepoutputs;
+ opt_simplecostmodel = _opt_simplecostmodel;
+ opt_nocostmodel = _opt_nocostmodel;
+ opt_instrumentmore = _opt_instrumentmore;
+
+ create_glift_logic();
+ }
+};
+
+struct GliftPass : public Pass {
+ GliftPass() : Pass("glift", "create GLIFT models and optimization problems") {}
+
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" glift <command> [options] [selection]\n");
+ log("\n");
+ log("Augments the current or specified module with gate-level information flow tracking\n");
+ log("(GLIFT) logic using the \"constructive mapping\" approach. Also can set up QBF-SAT\n");
+ log("optimization problems in order to optimize GLIFT models or trade off precision and\n");
+ log("complexity.\n");
+ log("\n");
+ log("\n");
+ log("Commands:\n");
+ log("\n");
+ log(" -create-precise-model\n");
+ log(" Replaces the current or specified module with one that has corresponding \"taint\"\n");
+ log(" inputs, outputs, and internal nets along with precise taint tracking logic.\n");
+ log(" For example, precise taint tracking logic for an AND gate is:\n");
+ log("\n");
+ log(" y_t = a & b_t | b & a_t | a_t & b_t\n");
+ log("\n");
+ log("\n");
+ log(" -create-imprecise-model\n");
+ log(" Replaces the current or specified module with one that has corresponding \"taint\"\n");
+ log(" inputs, outputs, and internal nets along with imprecise \"All OR\" taint tracking\n");
+ log(" logic:\n");
+ log("\n");
+ log(" y_t = a_t | b_t\n");
+ log("\n");
+ log("\n");
+ log(" -create-instrumented-model\n");
+ log(" Replaces the current or specified module with one that has corresponding \"taint\"\n");
+ log(" inputs, outputs, and internal nets along with 4 varying-precision versions of taint\n");
+ log(" tracking logic. Which version of taint tracking logic is used for a given gate is\n");
+ log(" determined by a MUX selected by an $anyconst cell. By default, unless the\n");
+ log(" `-no-cost-model` option is provided, an additional wire named `__glift_weight` with\n");
+ log(" the `keep` and `minimize` attributes is added to the module along with pmuxes and\n");
+ log(" adders to calculate a rough estimate of the number of logic gates in the GLIFT model\n");
+ log(" given an assignment for the $anyconst cells. The four versions of taint tracking logic\n");
+ log(" for an AND gate are:");
+ log("\n");
+ log(" y_t = a & b_t | b & a_t | a_t & b_t (like `-create-precise-model`)\n");
+ log(" y_t = a_t | a & b_t\n");
+ log(" y_t = b_t | b & a_t\n");
+ log(" y_t = a_t | b_t (like `-create-imprecise-model`)\n");
+ log("\n");
+ log("\n");
+ log("Options:\n");
+ log("\n");
+ log(" -taint-constants\n");
+ log(" Constant values in the design are labeled as tainted.\n");
+ log(" (default: label constants as un-tainted)\n");
+ log("\n");
+ log(" -keep-outputs\n");
+ log(" Do not remove module outputs. Taint tracking outputs will appear in the module ports\n");
+ log(" alongside the orignal outputs.\n");
+ log(" (default: original module outputs are removed)\n");
+ log("\n");
+ log(" -simple-cost-model\n");
+ log(" Do not model logic area. Instead model the number of non-zero assignments to $anyconsts.\n");
+ log(" Taint tracking logic versions vary in their size, but all reduced-precision versions are\n");
+ log(" significantly smaller than the fully-precise version. A non-zero $anyconst assignment means\n");
+ log(" that reduced-precision taint tracking logic was chosen for some gate.\n");
+ log(" Only applicable in combination with `-create-instrumented-model`.\n");
+ log(" (default: use a complex model and give that wire the \"keep\" and \"minimize\" attributes)\n");
+ log("\n");
+ log(" -no-cost-model\n");
+ log(" Do not model taint tracking logic area and do not create a `__glift_weight` wire.\n");
+ log(" Only applicable in combination with `-create-instrumented-model`.\n");
+ log(" (default: model area and give that wire the \"keep\" and \"minimize\" attributes)\n");
+ log("\n");
+ log(" -instrument-more\n");
+ log(" Allow choice from more versions of (even simpler) taint tracking logic. A total\n");
+ log(" of 8 versions of taint tracking logic will be added per gate, including the 4\n");
+ log(" versions from `-create-instrumented-model` and these additional versions:\n");
+ log("\n");
+ log(" y_t = a_t\n");
+ log(" y_t = b_t\n");
+ log(" y_t = 1\n");
+ log(" y_t = 0\n");
+ log("\n");
+ log(" Only applicable in combination with `-create-instrumented-model`.\n");
+ log(" (default: do not add more versions of taint tracking logic.\n");
+ log("\n");
+ }
+
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false;
+ bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false;
+ bool opt_instrumentmore = false;
+ log_header(design, "Executing GLIFT pass (creating and manipulating GLIFT models).\n");
+ std::vector<std::string>::size_type argidx;
+
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-create-precise-model") {
+ opt_create_precise_model = true;
+ continue;
+ }
+ if (args[argidx] == "-create-imprecise-model") {
+ opt_create_imprecise_model = true;
+ continue;
+ }
+ if (args[argidx] == "-create-instrumented-model") {
+ opt_create_instrumented_model = true;
+ continue;
+ }
+ if (args[argidx] == "-taint-constants") {
+ opt_taintconstants = true;
+ continue;
+ }
+ if (args[argidx] == "-keep-outputs") {
+ opt_keepoutputs = true;
+ continue;
+ }
+ if (args[argidx] == "-simple-cost-model") {
+ opt_simplecostmodel = true;
+ continue;
+ }
+ if (args[argidx] == "-no-cost-model") {
+ opt_nocostmodel = true;
+ continue;
+ }
+ if (args[argidx] == "-instrument-more") {
+ opt_instrumentmore = true;
+ continue;
+ }
+ break;
+ }
+ if(!opt_create_precise_model && !opt_create_imprecise_model && !opt_create_instrumented_model)
+ log_cmd_error("No command provided. See help for usage.\n");
+ if(static_cast<int>(opt_create_precise_model) + static_cast<int>(opt_create_imprecise_model) + static_cast<int>(opt_create_instrumented_model) != 1)
+ log_cmd_error("Only one command may be specified. See help for usage.\n");
+ if(opt_simplecostmodel && opt_nocostmodel)
+ log_cmd_error("Only one of `-simple-cost-model` and `-no-cost-model` may be specified. See help for usage.\n");
+ if((opt_simplecostmodel || opt_nocostmodel) && !opt_create_instrumented_model)
+ log_cmd_error("Options `-simple-cost-model` and `-no-cost-model` may only be used with `-create-instrumented-model`. See help for usage.\n");
+ extra_args(args, argidx, design);
+
+ if (GetSize(design->selected_modules()) == 0)
+ log_cmd_error("Can't operate on an empty selection!\n");
+
+ TopoSort<RTLIL::Module*, IdString::compare_ptr_by_name<RTLIL::Module>> topo_modules; //cribbed from passes/techmap/flatten.cc
+ auto worklist = design->selected_modules();
+ pool<RTLIL::IdString> non_top_modules;
+ while (!worklist.empty()) {
+ RTLIL::Module *module = *(worklist.begin());
+ worklist.erase(worklist.begin());
+ topo_modules.node(module);
+
+ for (auto cell : module->selected_cells()) {
+ RTLIL::Module *tpl = design->module(cell->type);
+ if (tpl != nullptr) {
+ if (topo_modules.database.count(tpl) == 0)
+ worklist.push_back(tpl);
+ topo_modules.edge(tpl, module);
+ non_top_modules.insert(cell->type);
+ }
+ }
+ }
+
+ if (!topo_modules.sort())
+ log_cmd_error("Cannot handle recursive module instantiations.\n");
+
+ for (auto i = 0; i < GetSize(topo_modules.sorted); ++i) {
+ RTLIL::Module *module = topo_modules.sorted[i];
+ GliftWorker(module, !non_top_modules[module->name], opt_create_precise_model, opt_create_imprecise_model, opt_create_instrumented_model, opt_taintconstants, opt_keepoutputs, opt_simplecostmodel, opt_nocostmodel, opt_instrumentmore);
+ }
+ }
+} GliftPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/cmds/select.cc b/passes/cmds/select.cc
index bb7b78cfe..b112b145c 100644
--- a/passes/cmds/select.cc
+++ b/passes/cmds/select.cc
@@ -944,12 +944,14 @@ static void select_stmt(RTLIL::Design *design, std::string arg, bool disable_emp
}
}
-static std::string describe_selection_for_assert(RTLIL::Design *design, RTLIL::Selection *sel)
+static std::string describe_selection_for_assert(RTLIL::Design *design, RTLIL::Selection *sel, bool whole_modules = false)
{
std::string desc = "Selection contains:\n";
for (auto mod : design->modules())
{
if (sel->selected_module(mod->name)) {
+ if (whole_modules && sel->selected_whole_module(mod->name))
+ desc += stringf("%s\n", id2cstr(mod->name));
for (auto wire : mod->wires())
if (sel->selected_member(mod->name, wire->name))
desc += stringf("%s/%s\n", id2cstr(mod->name), id2cstr(wire->name));
@@ -1051,17 +1053,17 @@ struct SelectPass : public Pass {
log("\n");
log(" -unset <name>\n");
log(" do not modify the current selection. instead remove a previously saved\n");
- log(" selection under the given name (see @<name> below).");
+ log(" selection under the given name (see @<name> below).\n");
log("\n");
log(" -assert-none\n");
log(" do not modify the current selection. instead assert that the given\n");
- log(" selection is empty. i.e. produce an error if any object matching the\n");
- log(" selection is found.\n");
+ log(" selection is empty. i.e. produce an error if any object or module\n");
+ log(" matching the selection is found.\n");
log("\n");
log(" -assert-any\n");
log(" do not modify the current selection. instead assert that the given\n");
- log(" selection is non-empty. i.e. produce an error if no object matching\n");
- log(" the selection is found.\n");
+ log(" selection is non-empty. i.e. produce an error if no object or module\n");
+ log(" matching the selection is found.\n");
log("\n");
log(" -assert-count N\n");
log(" do not modify the current selection. instead assert that the given\n");
@@ -1454,7 +1456,10 @@ struct SelectPass : public Pass {
}
}
if (count_mode)
+ {
+ design->scratchpad_set_int("select.count", total_count);
log("%d objects.\n", total_count);
+ }
if (f != nullptr)
fclose(f);
#undef LOG_OBJECT
@@ -1488,7 +1493,7 @@ struct SelectPass : public Pass {
{
RTLIL::Selection *sel = &work_stack.back();
sel->optimize(design);
- std::string desc = describe_selection_for_assert(design, sel);
+ std::string desc = describe_selection_for_assert(design, sel, true);
log_error("Assertion failed: selection is not empty:%s\n%s", sel_str.c_str(), desc.c_str());
}
return;
@@ -1503,7 +1508,7 @@ struct SelectPass : public Pass {
{
RTLIL::Selection *sel = &work_stack.back();
sel->optimize(design);
- std::string desc = describe_selection_for_assert(design, sel);
+ std::string desc = describe_selection_for_assert(design, sel, true);
log_error("Assertion failed: selection is empty:%s\n%s", sel_str.c_str(), desc.c_str());
}
return;
diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc
index 8f9824f9b..43deba47b 100644
--- a/passes/cmds/show.cc
+++ b/passes/cmds/show.cc
@@ -52,7 +52,7 @@ struct ShowWorker
std::map<RTLIL::IdString, int> autonames;
int single_idx_count;
- struct net_conn { std::set<std::string> in, out; int bits; std::string color; };
+ struct net_conn { std::set<std::pair<std::string, int>> in, out; std::string color; };
std::map<std::string, net_conn> net_conn_map;
FILE *f;
@@ -191,7 +191,12 @@ struct ShowWorker
std::string str;
for (char ch : id) {
- if (ch == '\\' || ch == '"')
+ if (ch == '\\') {
+ // new graphviz have bug with escaping '\'
+ str += "&#9586;";
+ continue;
+ }
+ if (ch == '"')
str += "\\";
str += ch;
}
@@ -268,8 +273,7 @@ struct ShowWorker
if (driver) {
log_assert(!net.empty());
label_string += stringf("<s%d> %d:%d - %s%d:%d |", i, pos, pos-c.width+1, repinfo.c_str(), cl, cr);
- net_conn_map[net].in.insert(stringf("x%d:s%d", idx, i));
- net_conn_map[net].bits = rep*c.width;
+ net_conn_map[net].in.insert({stringf("x%d:s%d", idx, i), rep*c.width});
net_conn_map[net].color = nextColor(c, net_conn_map[net].color);
} else
if (net.empty()) {
@@ -282,8 +286,7 @@ struct ShowWorker
pos, pos-rep*c.width+1);
} else {
label_string += stringf("<s%d> %s%d:%d - %d:%d |", i, repinfo.c_str(), cl, cr, pos, pos-rep*c.width+1);
- net_conn_map[net].out.insert(stringf("x%d:s%d", idx, i));
- net_conn_map[net].bits = rep*c.width;
+ net_conn_map[net].out.insert({stringf("x%d:s%d", idx, i), rep*c.width});
net_conn_map[net].color = nextColor(c, net_conn_map[net].color);
}
pos -= rep * c.width;
@@ -293,6 +296,7 @@ struct ShowWorker
code += stringf("x%d [ shape=record, style=rounded, label=\"%s\" ];\n", idx, label_string.c_str());
if (!port.empty()) {
currentColor = xorshift32(currentColor);
+ log_warning("WIDTHLABEL %s %d\n", log_signal(sig), GetSize(sig));
if (driver)
code += stringf("%s:e -> x%d:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", port.c_str(), idx, nextColor(sig).c_str(), widthLabel(sig.size()).c_str());
else
@@ -305,10 +309,9 @@ struct ShowWorker
{
if (!port.empty()) {
if (driver)
- net_conn_map[net].in.insert(port);
+ net_conn_map[net].in.insert({port, GetSize(sig)});
else
- net_conn_map[net].out.insert(port);
- net_conn_map[net].bits = sig.size();
+ net_conn_map[net].out.insert({port, GetSize(sig)});
net_conn_map[net].color = nextColor(sig, net_conn_map[net].color);
}
if (node != nullptr)
@@ -478,8 +481,7 @@ struct ShowWorker
std::string code, node;
code += gen_portbox("", sig, false, &node);
fprintf(f, "%s", code.c_str());
- net_conn_map[node].out.insert(stringf("p%d", pidx));
- net_conn_map[node].bits = sig.size();
+ net_conn_map[node].out.insert({stringf("p%d", pidx), GetSize(sig)});
net_conn_map[node].color = nextColor(sig, net_conn_map[node].color);
}
@@ -487,8 +489,7 @@ struct ShowWorker
std::string code, node;
code += gen_portbox("", sig, true, &node);
fprintf(f, "%s", code.c_str());
- net_conn_map[node].in.insert(stringf("p%d", pidx));
- net_conn_map[node].bits = sig.size();
+ net_conn_map[node].in.insert({stringf("p%d", pidx), GetSize(sig)});
net_conn_map[node].color = nextColor(sig, net_conn_map[node].color);
}
@@ -522,17 +523,15 @@ struct ShowWorker
currentColor = xorshift32(currentColor);
fprintf(f, "%s:e -> %s:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", left_node.c_str(), right_node.c_str(), nextColor(conn).c_str(), widthLabel(conn.first.size()).c_str());
} else {
- net_conn_map[right_node].bits = conn.first.size();
net_conn_map[right_node].color = nextColor(conn, net_conn_map[right_node].color);
- net_conn_map[left_node].bits = conn.first.size();
net_conn_map[left_node].color = nextColor(conn, net_conn_map[left_node].color);
if (left_node[0] == 'x') {
- net_conn_map[right_node].in.insert(left_node);
+ net_conn_map[right_node].in.insert({left_node, GetSize(conn.first)});
} else if (right_node[0] == 'x') {
- net_conn_map[left_node].out.insert(right_node);
+ net_conn_map[left_node].out.insert({right_node, GetSize(conn.first)});
} else {
- net_conn_map[right_node].in.insert(stringf("x%d:e", single_idx_count));
- net_conn_map[left_node].out.insert(stringf("x%d:w", single_idx_count));
+ net_conn_map[right_node].in.insert({stringf("x%d:e", single_idx_count), GetSize(conn.first)});
+ net_conn_map[left_node].out.insert({stringf("x%d:w", single_idx_count), GetSize(conn.first)});
fprintf(f, "x%d [shape=box, style=rounded, label=\"BUF\"];\n", single_idx_count++);
}
}
@@ -542,12 +541,13 @@ struct ShowWorker
{
currentColor = xorshift32(currentColor);
if (wires_on_demand.count(it.first) > 0) {
- if (it.second.in.size() == 1 && it.second.out.size() > 1 && it.second.in.begin()->compare(0, 1, "p") == 0)
+ if (it.second.in.size() == 1 && it.second.out.size() > 1 && it.second.in.begin()->first.compare(0, 1, "p") == 0)
it.second.out.erase(*it.second.in.begin());
if (it.second.in.size() == 1 && it.second.out.size() == 1) {
- std::string from = *it.second.in.begin(), to = *it.second.out.begin();
+ std::string from = it.second.in.begin()->first, to = it.second.out.begin()->first;
+ int bits = it.second.in.begin()->second;
if (from != to || from.compare(0, 1, "p") != 0)
- fprintf(f, "%s:e -> %s:w [%s, %s];\n", from.c_str(), to.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str());
+ fprintf(f, "%s:e -> %s:w [%s, %s];\n", from.c_str(), to.c_str(), nextColor(it.second.color).c_str(), widthLabel(bits).c_str());
continue;
}
if (it.second.in.size() == 0 || it.second.out.size() == 0)
@@ -556,9 +556,9 @@ struct ShowWorker
fprintf(f, "%s [ shape=point ];\n", it.first.c_str());
}
for (auto &it2 : it.second.in)
- fprintf(f, "%s:e -> %s:w [%s, %s];\n", it2.c_str(), it.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str());
+ fprintf(f, "%s:e -> %s:w [%s, %s];\n", it2.first.c_str(), it.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it2.second).c_str());
for (auto &it2 : it.second.out)
- fprintf(f, "%s:e -> %s:w [%s, %s];\n", it.first.c_str(), it2.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str());
+ fprintf(f, "%s:e -> %s:w [%s, %s];\n", it.first.c_str(), it2.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it2.second).c_str());
}
fprintf(f, "}\n");
diff --git a/passes/cmds/sta.cc b/passes/cmds/sta.cc
index 13e1ee13c..4ad0e96be 100644
--- a/passes/cmds/sta.cc
+++ b/passes/cmds/sta.cc
@@ -58,11 +58,14 @@ struct StaWorker
{
TimingInfo timing;
+ pool<IdString> unrecognised_cells;
+
for (auto cell : module->cells())
{
Module *inst_module = design->module(cell->type);
if (!inst_module) {
- log_warning("Cell type '%s' not recognised! Ignoring.\n", log_id(cell->type));
+ if (unrecognised_cells.insert(cell->type).second)
+ log_warning("Cell type '%s' not recognised! Ignoring.\n", log_id(cell->type));
continue;
}
diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc
index 14a27ed99..c858c8631 100644
--- a/passes/cmds/stat.cc
+++ b/passes/cmds/stat.cc
@@ -117,6 +117,10 @@ struct statdata_t
}
else if (cell_type.in(ID($mux), ID($pmux)))
cell_type = stringf("%s_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y)));
+ else if (cell_type == ID($bmux))
+ cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y)), GetSize(cell->getPort(ID::S)));
+ else if (cell_type == ID($demux))
+ cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::S)));
else if (cell_type.in(
ID($sr), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre),
ID($adff), ID($adffe), ID($sdff), ID($sdffe), ID($sdffce),
@@ -377,6 +381,15 @@ struct StatPass : public Pass {
log("\n");
data.log_data(top_mod->name, true);
+ design->scratchpad_set_int("stat.num_wires", data.num_wires);
+ design->scratchpad_set_int("stat.num_wire_bits", data.num_wire_bits);
+ design->scratchpad_set_int("stat.num_pub_wires", data.num_pub_wires);
+ design->scratchpad_set_int("stat.num_pub_wire_bits", data.num_pub_wire_bits);
+ design->scratchpad_set_int("stat.num_memories", data.num_memories);
+ design->scratchpad_set_int("stat.num_memory_bits", data.num_memory_bits);
+ design->scratchpad_set_int("stat.num_processes", data.num_processes);
+ design->scratchpad_set_int("stat.num_cells", data.num_cells);
+ design->scratchpad_set_int("stat.area", data.area);
}
log("\n");
diff --git a/passes/fsm/fsmdata.h b/passes/fsm/fsmdata.h
index 4ba3b4e4f..97371efab 100644
--- a/passes/fsm/fsmdata.h
+++ b/passes/fsm/fsmdata.h
@@ -91,8 +91,8 @@ struct FsmData
if (reset_state < 0 || reset_state >= state_num)
reset_state = -1;
- RTLIL::Const state_table = cell->parameters[ID::STATE_TABLE];
- RTLIL::Const trans_table = cell->parameters[ID::TRANS_TABLE];
+ const RTLIL::Const &state_table = cell->parameters[ID::STATE_TABLE];
+ const RTLIL::Const &trans_table = cell->parameters[ID::TRANS_TABLE];
for (int i = 0; i < state_num; i++) {
RTLIL::Const state_code;
diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc
index 440881f19..d40d6e59f 100644
--- a/passes/hierarchy/hierarchy.cc
+++ b/passes/hierarchy/hierarchy.cc
@@ -976,6 +976,19 @@ struct HierarchyPass : public Pass {
if (mod->get_bool_attribute(ID::top))
top_mod = mod;
+ if (top_mod == nullptr && auto_top_mode) {
+ log_header(design, "Finding top of design hierarchy..\n");
+ dict<Module*, int> db;
+ for (Module *mod : design->selected_modules()) {
+ int score = find_top_mod_score(design, mod, db);
+ log("root of %3d design levels: %-20s\n", score, log_id(mod));
+ if (!top_mod || score > db[top_mod])
+ top_mod = mod;
+ }
+ if (top_mod != nullptr)
+ log("Automatically selected %s as design top module.\n", log_id(top_mod));
+ }
+
if (top_mod != nullptr && top_mod->name.begins_with("$abstract")) {
IdString top_name = top_mod->name.substr(strlen("$abstract"));
@@ -1000,19 +1013,6 @@ struct HierarchyPass : public Pass {
}
}
- if (top_mod == nullptr && auto_top_mode) {
- log_header(design, "Finding top of design hierarchy..\n");
- dict<Module*, int> db;
- for (Module *mod : design->selected_modules()) {
- int score = find_top_mod_score(design, mod, db);
- log("root of %3d design levels: %-20s\n", score, log_id(mod));
- if (!top_mod || score > db[top_mod])
- top_mod = mod;
- }
- if (top_mod != nullptr)
- log("Automatically selected %s as design top module.\n", log_id(top_mod));
- }
-
if (flag_simcheck && top_mod == nullptr)
log_error("Design has no top module.\n");
diff --git a/passes/memory/Makefile.inc b/passes/memory/Makefile.inc
index 5a2c4ecfc..d9dca52df 100644
--- a/passes/memory/Makefile.inc
+++ b/passes/memory/Makefile.inc
@@ -9,4 +9,7 @@ OBJS += passes/memory/memory_map.o
OBJS += passes/memory/memory_memx.o
OBJS += passes/memory/memory_nordff.o
OBJS += passes/memory/memory_narrow.o
+OBJS += passes/memory/memory_libmap.o
+OBJS += passes/memory/memory_bmux2rom.o
+OBJS += passes/memory/memlib.o
diff --git a/passes/memory/memlib.cc b/passes/memory/memlib.cc
new file mode 100644
index 000000000..8a7adc9ac
--- /dev/null
+++ b/passes/memory/memlib.cc
@@ -0,0 +1,1101 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2021 Marcelina Koƛcielnicka <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 "memlib.h"
+
+#include <ctype.h>
+
+USING_YOSYS_NAMESPACE
+
+using namespace MemLibrary;
+
+PRIVATE_NAMESPACE_BEGIN
+
+typedef dict<std::string, Const> Options;
+
+struct ClockDef {
+ ClkPolKind kind;
+ std::string name;
+};
+
+struct RawWrTransDef {
+ WrTransTargetKind target_kind;
+ std::string target_group;
+ WrTransKind kind;
+};
+
+struct PortWidthDef {
+ bool tied;
+ std::vector<int> wr_widths;
+ std::vector<int> rd_widths;
+};
+
+struct SrstDef {
+ ResetValKind val;
+ SrstKind kind;
+ bool block_wr;
+};
+
+struct Empty {};
+
+template<typename T> struct Capability {
+ T val;
+ Options opts, portopts;
+
+ Capability(T val, Options opts, Options portopts) : val(val), opts(opts), portopts(portopts) {}
+};
+
+template<typename T> using Caps = std::vector<Capability<T>>;
+
+struct PortGroupDef {
+ PortKind kind;
+ dict<std::string, pool<Const>> portopts;
+ std::vector<std::string> names;
+ Caps<Empty> forbid;
+ Caps<ClockDef> clock;
+ Caps<Empty> clken;
+ Caps<Empty> wrbe_separate;
+ Caps<PortWidthDef> width;
+ Caps<Empty> rden;
+ Caps<RdWrKind> rdwr;
+ Caps<ResetValKind> rdinit;
+ Caps<ResetValKind> rdarst;
+ Caps<SrstDef> rdsrst;
+ Caps<std::string> wrprio;
+ Caps<RawWrTransDef> wrtrans;
+ Caps<Empty> optional;
+ Caps<Empty> optional_rw;
+};
+
+struct WidthsDef {
+ std::vector<int> widths;
+ WidthMode mode;
+};
+
+struct ResourceDef {
+ std::string name;
+ int count;
+};
+
+struct RamDef {
+ IdString id;
+ dict<std::string, pool<Const>> opts;
+ RamKind kind;
+ Caps<Empty> forbid;
+ Caps<Empty> prune_rom;
+ Caps<PortGroupDef> ports;
+ Caps<int> abits;
+ Caps<WidthsDef> widths;
+ Caps<ResourceDef> resource;
+ Caps<double> cost;
+ Caps<double> widthscale;
+ Caps<int> byte;
+ Caps<MemoryInitKind> init;
+ Caps<std::string> style;
+};
+
+struct Parser {
+ std::string filename;
+ std::ifstream infile;
+ int line_number = 0;
+ Library &lib;
+ const pool<std::string> &defines;
+ pool<std::string> &defines_unused;
+ std::vector<std::string> tokens;
+ int token_idx = 0;
+ bool eof = false;
+
+ std::vector<std::pair<std::string, Const>> option_stack;
+ std::vector<std::pair<std::string, Const>> portoption_stack;
+ RamDef ram;
+ PortGroupDef port;
+ bool active = true;
+
+ Parser(std::string filename, Library &lib, const pool<std::string> &defines, pool<std::string> &defines_unused) : filename(filename), lib(lib), defines(defines), defines_unused(defines_unused) {
+ // Note: this rewrites the filename we're opening, but not
+ // the one we're storing — this is actually correct, so that
+ // we keep the original filename for diagnostics.
+ rewrite_filename(filename);
+ infile.open(filename);
+ if (infile.fail()) {
+ log_error("failed to open %s\n", filename.c_str());
+ }
+ parse();
+ infile.close();
+ }
+
+ std::string peek_token() {
+ if (eof)
+ return "";
+
+ if (token_idx < GetSize(tokens))
+ return tokens[token_idx];
+
+ tokens.clear();
+ token_idx = 0;
+
+ std::string line;
+ while (std::getline(infile, line)) {
+ line_number++;
+ for (string tok = next_token(line); !tok.empty(); tok = next_token(line)) {
+ if (tok[0] == '#')
+ break;
+ if (tok[tok.size()-1] == ';') {
+ tokens.push_back(tok.substr(0, tok.size()-1));
+ tokens.push_back(";");
+ } else {
+ tokens.push_back(tok);
+ }
+ }
+ if (!tokens.empty())
+ return tokens[token_idx];
+ }
+
+ eof = true;
+ return "";
+ }
+
+ std::string get_token() {
+ std::string res = peek_token();
+ if (!eof)
+ token_idx++;
+ return res;
+ }
+
+ void eat_token(std::string expected) {
+ std::string token = get_token();
+ if (token != expected) {
+ log_error("%s:%d: expected `%s`, got `%s`.\n", filename.c_str(), line_number, expected.c_str(), token.c_str());
+ }
+ }
+
+ IdString get_id() {
+ std::string token = get_token();
+ if (token.empty() || (token[0] != '$' && token[0] != '\\')) {
+ log_error("%s:%d: expected id string, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ return IdString(token);
+ }
+
+ std::string get_name() {
+ std::string res = get_token();
+ bool valid = true;
+ // Basic sanity check.
+ if (res.empty() || (!isalpha(res[0]) && res[0] != '_'))
+ valid = false;
+ for (char c: res)
+ if (!isalnum(c) && c != '_')
+ valid = false;
+ if (!valid)
+ log_error("%s:%d: expected name, got `%s`.\n", filename.c_str(), line_number, res.c_str());
+ return res;
+ }
+
+ std::string get_string() {
+ std::string token = get_token();
+ if (token.size() < 2 || token[0] != '"' || token[token.size()-1] != '"') {
+ log_error("%s:%d: expected string, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ return token.substr(1, token.size()-2);
+ }
+
+ bool peek_string() {
+ std::string token = peek_token();
+ return !token.empty() && token[0] == '"';
+ }
+
+ int get_int() {
+ std::string token = get_token();
+ char *endptr;
+ long res = strtol(token.c_str(), &endptr, 0);
+ if (token.empty() || *endptr || res > INT_MAX) {
+ log_error("%s:%d: expected int, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ return res;
+ }
+
+ double get_double() {
+ std::string token = get_token();
+ char *endptr;
+ double res = strtod(token.c_str(), &endptr);
+ if (token.empty() || *endptr) {
+ log_error("%s:%d: expected float, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ return res;
+ }
+
+ bool peek_int() {
+ std::string token = peek_token();
+ return !token.empty() && isdigit(token[0]);
+ }
+
+ void get_semi() {
+ std::string token = get_token();
+ if (token != ";") {
+ log_error("%s:%d: expected `;`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ }
+
+ Const get_value() {
+ std::string token = peek_token();
+ if (!token.empty() && token[0] == '"') {
+ std::string s = get_string();
+ return Const(s);
+ } else {
+ return Const(get_int());
+ }
+ }
+
+ bool enter_ifdef(bool polarity) {
+ bool res = active;
+ std::string name = get_name();
+ defines_unused.erase(name);
+ if (active) {
+ if (defines.count(name)) {
+ active = polarity;
+ } else {
+ active = !polarity;
+ }
+ }
+ return res;
+ }
+
+ void enter_else(bool save) {
+ get_token();
+ active = !active && save;
+ }
+
+ void enter_option() {
+ std::string name = get_string();
+ Const val = get_value();
+ if (active) {
+ ram.opts[name].insert(val);
+ }
+ option_stack.push_back({name, val});
+ }
+
+ void exit_option() {
+ option_stack.pop_back();
+ }
+
+ Options get_options() {
+ Options res;
+ for (auto it: option_stack)
+ res[it.first] = it.second;
+ return res;
+ }
+
+ void enter_portoption() {
+ std::string name = get_string();
+ Const val = get_value();
+ if (active) {
+ port.portopts[name].insert(val);
+ }
+ portoption_stack.push_back({name, val});
+ }
+
+ void exit_portoption() {
+ portoption_stack.pop_back();
+ }
+
+ Options get_portoptions() {
+ Options res;
+ for (auto it: portoption_stack)
+ res[it.first] = it.second;
+ return res;
+ }
+
+ template<typename T> void add_cap(Caps<T> &caps, T val) {
+ if (active)
+ caps.push_back(Capability<T>(val, get_options(), get_portoptions()));
+ }
+
+ void parse_port_block() {
+ if (peek_token() == "{") {
+ get_token();
+ while (peek_token() != "}")
+ parse_port_item();
+ get_token();
+ } else {
+ parse_port_item();
+ }
+ }
+
+ void parse_ram_block() {
+ if (peek_token() == "{") {
+ get_token();
+ while (peek_token() != "}")
+ parse_ram_item();
+ get_token();
+ } else {
+ parse_ram_item();
+ }
+ }
+
+ void parse_top_block() {
+ if (peek_token() == "{") {
+ get_token();
+ while (peek_token() != "}")
+ parse_top_item();
+ get_token();
+ } else {
+ parse_top_item();
+ }
+ }
+
+ void parse_port_item() {
+ std::string token = get_token();
+ if (token == "ifdef") {
+ bool save = enter_ifdef(true);
+ parse_port_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_port_block();
+ }
+ active = save;
+ } else if (token == "ifndef") {
+ bool save = enter_ifdef(false);
+ parse_port_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_port_block();
+ }
+ active = save;
+ } else if (token == "option") {
+ enter_option();
+ parse_port_block();
+ exit_option();
+ } else if (token == "portoption") {
+ enter_portoption();
+ parse_port_block();
+ exit_portoption();
+ } else if (token == "clock") {
+ if (port.kind == PortKind::Ar) {
+ log_error("%s:%d: `clock` not allowed in async read port.\n", filename.c_str(), line_number);
+ }
+ ClockDef def;
+ token = get_token();
+ if (token == "anyedge") {
+ def.kind = ClkPolKind::Anyedge;
+ } else if (token == "posedge") {
+ def.kind = ClkPolKind::Posedge;
+ } else if (token == "negedge") {
+ def.kind = ClkPolKind::Negedge;
+ } else {
+ log_error("%s:%d: expected `posedge`, `negedge`, or `anyedge`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ if (peek_string()) {
+ def.name = get_string();
+ }
+ get_semi();
+ add_cap(port.clock, def);
+ } else if (token == "clken") {
+ if (port.kind == PortKind::Ar) {
+ log_error("%s:%d: `clken` not allowed in async read port.\n", filename.c_str(), line_number);
+ }
+ get_semi();
+ add_cap(port.clken, {});
+ } else if (token == "wrbe_separate") {
+ if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) {
+ log_error("%s:%d: `wrbe_separate` not allowed in read port.\n", filename.c_str(), line_number);
+ }
+ get_semi();
+ add_cap(port.wrbe_separate, {});
+ } else if (token == "width") {
+ PortWidthDef def;
+ token = peek_token();
+ bool is_rw = port.kind == PortKind::Srsw || port.kind == PortKind::Arsw;
+ if (token == "tied") {
+ get_token();
+ if (!is_rw)
+ log_error("%s:%d: `tied` only makes sense for read+write ports.\n", filename.c_str(), line_number);
+ while (peek_int())
+ def.wr_widths.push_back(get_int());
+ def.tied = true;
+ } else if (token == "mix") {
+ get_token();
+ if (!is_rw)
+ log_error("%s:%d: `mix` only makes sense for read+write ports.\n", filename.c_str(), line_number);
+ while (peek_int())
+ def.wr_widths.push_back(get_int());
+ def.rd_widths = def.wr_widths;
+ def.tied = false;
+ } else if (token == "rd") {
+ get_token();
+ if (!is_rw)
+ log_error("%s:%d: `rd` only makes sense for read+write ports.\n", filename.c_str(), line_number);
+ do {
+ def.rd_widths.push_back(get_int());
+ } while (peek_int());
+ eat_token("wr");
+ do {
+ def.wr_widths.push_back(get_int());
+ } while (peek_int());
+ def.tied = false;
+ } else if (token == "wr") {
+ get_token();
+ if (!is_rw)
+ log_error("%s:%d: `wr` only makes sense for read+write ports.\n", filename.c_str(), line_number);
+ do {
+ def.wr_widths.push_back(get_int());
+ } while (peek_int());
+ eat_token("rd");
+ do {
+ def.rd_widths.push_back(get_int());
+ } while (peek_int());
+ def.tied = false;
+ } else {
+ do {
+ def.wr_widths.push_back(get_int());
+ } while (peek_int());
+ def.tied = true;
+ }
+ get_semi();
+ add_cap(port.width, def);
+ } else if (token == "rden") {
+ if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
+ log_error("%s:%d: `rden` only allowed on sync read ports.\n", filename.c_str(), line_number);
+ get_semi();
+ add_cap(port.rden, {});
+ } else if (token == "rdwr") {
+ if (port.kind != PortKind::Srsw)
+ log_error("%s:%d: `rdwr` only allowed on sync read+write ports.\n", filename.c_str(), line_number);
+ RdWrKind kind;
+ token = get_token();
+ if (token == "undefined") {
+ kind = RdWrKind::Undefined;
+ } else if (token == "no_change") {
+ kind = RdWrKind::NoChange;
+ } else if (token == "new") {
+ kind = RdWrKind::New;
+ } else if (token == "old") {
+ kind = RdWrKind::Old;
+ } else if (token == "new_only") {
+ kind = RdWrKind::NewOnly;
+ } else {
+ log_error("%s:%d: expected `undefined`, `new`, `old`, `new_only`, or `no_change`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(port.rdwr, kind);
+ } else if (token == "rdinit") {
+ if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
+ log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
+ ResetValKind kind;
+ token = get_token();
+ if (token == "none") {
+ kind = ResetValKind::None;
+ } else if (token == "zero") {
+ kind = ResetValKind::Zero;
+ } else if (token == "any") {
+ kind = ResetValKind::Any;
+ } else if (token == "no_undef") {
+ kind = ResetValKind::NoUndef;
+ } else {
+ log_error("%s:%d: expected `none`, `zero`, `any`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(port.rdinit, kind);
+ } else if (token == "rdarst") {
+ if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
+ log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
+ ResetValKind kind;
+ token = get_token();
+ if (token == "none") {
+ kind = ResetValKind::None;
+ } else if (token == "zero") {
+ kind = ResetValKind::Zero;
+ } else if (token == "any") {
+ kind = ResetValKind::Any;
+ } else if (token == "no_undef") {
+ kind = ResetValKind::NoUndef;
+ } else if (token == "init") {
+ kind = ResetValKind::Init;
+ } else {
+ log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(port.rdarst, kind);
+ } else if (token == "rdsrst") {
+ if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
+ log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
+ SrstDef def;
+ token = get_token();
+ if (token == "none") {
+ def.val = ResetValKind::None;
+ } else if (token == "zero") {
+ def.val = ResetValKind::Zero;
+ } else if (token == "any") {
+ def.val = ResetValKind::Any;
+ } else if (token == "no_undef") {
+ def.val = ResetValKind::NoUndef;
+ } else if (token == "init") {
+ def.val = ResetValKind::Init;
+ } else {
+ log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ if (def.val == ResetValKind::None) {
+ def.kind = SrstKind::None;
+ } else {
+ token = get_token();
+ if (token == "ungated") {
+ def.kind = SrstKind::Ungated;
+ } else if (token == "gated_clken") {
+ def.kind = SrstKind::GatedClkEn;
+ } else if (token == "gated_rden") {
+ def.kind = SrstKind::GatedRdEn;
+ } else {
+ log_error("%s:%d: expected `ungated`, `gated_clken` or `gated_rden`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ }
+ def.block_wr = false;
+ if (peek_token() == "block_wr") {
+ get_token();
+ def.block_wr = true;
+ }
+ get_semi();
+ add_cap(port.rdsrst, def);
+ } else if (token == "wrprio") {
+ if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
+ log_error("%s:%d: `wrprio` only allowed on write ports.\n", filename.c_str(), line_number);
+ do {
+ add_cap(port.wrprio, get_string());
+ } while (peek_string());
+ get_semi();
+ } else if (token == "wrtrans") {
+ if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
+ log_error("%s:%d: `wrtrans` only allowed on write ports.\n", filename.c_str(), line_number);
+ token = peek_token();
+ RawWrTransDef def;
+ if (token == "all") {
+ def.target_kind = WrTransTargetKind::All;
+ get_token();
+ } else {
+ def.target_kind = WrTransTargetKind::Group;
+ def.target_group = get_string();
+ }
+ token = get_token();
+ if (token == "new") {
+ def.kind = WrTransKind::New;
+ } else if (token == "old") {
+ def.kind = WrTransKind::Old;
+ } else {
+ log_error("%s:%d: expected `new` or `old`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(port.wrtrans, def);
+ } else if (token == "forbid") {
+ get_semi();
+ add_cap(port.forbid, {});
+ } else if (token == "optional") {
+ get_semi();
+ add_cap(port.optional, {});
+ } else if (token == "optional_rw") {
+ get_semi();
+ add_cap(port.optional_rw, {});
+ } else if (token == "") {
+ log_error("%s:%d: unexpected EOF while parsing port item.\n", filename.c_str(), line_number);
+ } else {
+ log_error("%s:%d: unknown port-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ }
+
+ void parse_ram_item() {
+ std::string token = get_token();
+ if (token == "ifdef") {
+ bool save = enter_ifdef(true);
+ parse_ram_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_ram_block();
+ }
+ active = save;
+ } else if (token == "ifndef") {
+ bool save = enter_ifdef(false);
+ parse_ram_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_ram_block();
+ }
+ active = save;
+ } else if (token == "option") {
+ enter_option();
+ parse_ram_block();
+ exit_option();
+ } else if (token == "prune_rom") {
+ get_semi();
+ add_cap(ram.prune_rom, {});
+ } else if (token == "forbid") {
+ get_semi();
+ add_cap(ram.forbid, {});
+ } else if (token == "abits") {
+ int val = get_int();
+ if (val < 0)
+ log_error("%s:%d: abits %d nagative.\n", filename.c_str(), line_number, val);
+ get_semi();
+ add_cap(ram.abits, val);
+ } else if (token == "width") {
+ WidthsDef def;
+ int w = get_int();
+ if (w <= 0)
+ log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w);
+ def.widths.push_back(w);
+ def.mode = WidthMode::Single;
+ get_semi();
+ add_cap(ram.widths, def);
+ } else if (token == "widths") {
+ WidthsDef def;
+ int last = 0;
+ do {
+ int w = get_int();
+ if (w <= 0)
+ log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w);
+ if (w < last * 2)
+ log_error("%s:%d: width %d smaller than %d required for progression.\n", filename.c_str(), line_number, w, last * 2);
+ last = w;
+ def.widths.push_back(w);
+ } while(peek_int());
+ token = get_token();
+ if (token == "global") {
+ def.mode = WidthMode::Global;
+ } else if (token == "per_port") {
+ def.mode = WidthMode::PerPort;
+ } else {
+ log_error("%s:%d: expected `global`, or `per_port`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(ram.widths, def);
+ } else if (token == "resource") {
+ ResourceDef def;
+ def.name = get_string();
+ if (peek_int())
+ def.count = get_int();
+ else
+ def.count = 1;
+ get_semi();
+ add_cap(ram.resource, def);
+ } else if (token == "cost") {
+ add_cap(ram.cost, get_double());
+ get_semi();
+ } else if (token == "widthscale") {
+ if (peek_int()) {
+ add_cap(ram.widthscale, get_double());
+ } else {
+ add_cap(ram.widthscale, 0.0);
+ }
+ get_semi();
+ } else if (token == "byte") {
+ int val = get_int();
+ if (val <= 0)
+ log_error("%s:%d: byte width %d not positive.\n", filename.c_str(), line_number, val);
+ add_cap(ram.byte, val);
+ get_semi();
+ } else if (token == "init") {
+ MemoryInitKind kind;
+ token = get_token();
+ if (token == "zero") {
+ kind = MemoryInitKind::Zero;
+ } else if (token == "any") {
+ kind = MemoryInitKind::Any;
+ } else if (token == "no_undef") {
+ kind = MemoryInitKind::NoUndef;
+ } else if (token == "none") {
+ kind = MemoryInitKind::None;
+ } else {
+ log_error("%s:%d: expected `zero`, `any`, `none`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ get_semi();
+ add_cap(ram.init, kind);
+ } else if (token == "style") {
+ do {
+ std::string val = get_string();
+ for (auto &c: val)
+ c = std::tolower(c);
+ add_cap(ram.style, val);
+ } while (peek_string());
+ get_semi();
+ } else if (token == "port") {
+ port = PortGroupDef();
+ token = get_token();
+ if (token == "ar") {
+ port.kind = PortKind::Ar;
+ } else if (token == "sr") {
+ port.kind = PortKind::Sr;
+ } else if (token == "sw") {
+ port.kind = PortKind::Sw;
+ } else if (token == "arsw") {
+ port.kind = PortKind::Arsw;
+ } else if (token == "srsw") {
+ port.kind = PortKind::Srsw;
+ } else {
+ log_error("%s:%d: expected `ar`, `sr`, `sw`, `arsw`, or `srsw`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ do {
+ port.names.push_back(get_string());
+ } while (peek_string());
+ parse_port_block();
+ if (active)
+ add_cap(ram.ports, port);
+ } else if (token == "") {
+ log_error("%s:%d: unexpected EOF while parsing ram item.\n", filename.c_str(), line_number);
+ } else {
+ log_error("%s:%d: unknown ram-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ }
+
+ void parse_top_item() {
+ std::string token = get_token();
+ if (token == "ifdef") {
+ bool save = enter_ifdef(true);
+ parse_top_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_top_block();
+ }
+ active = save;
+ } else if (token == "ifndef") {
+ bool save = enter_ifdef(false);
+ parse_top_block();
+ if (peek_token() == "else") {
+ enter_else(save);
+ parse_top_block();
+ }
+ active = save;
+ } else if (token == "ram") {
+ int orig_line = line_number;
+ ram = RamDef();
+ token = get_token();
+ if (token == "distributed") {
+ ram.kind = RamKind::Distributed;
+ } else if (token == "block") {
+ ram.kind = RamKind::Block;
+ } else if (token == "huge") {
+ ram.kind = RamKind::Huge;
+ } else {
+ log_error("%s:%d: expected `distributed`, `block`, or `huge`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ ram.id = get_id();
+ parse_ram_block();
+ if (active) {
+ compile_ram(orig_line);
+ }
+ } else if (token == "") {
+ log_error("%s:%d: unexpected EOF while parsing top item.\n", filename.c_str(), line_number);
+ } else {
+ log_error("%s:%d: unknown top-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
+ }
+ }
+
+ bool opts_ok(const Options &def, const Options &var) {
+ for (auto &it: def)
+ if (var.at(it.first) != it.second)
+ return false;
+ return true;
+ }
+
+ template<typename T> const T *find_single_cap(const Caps<T> &caps, const Options &opts, const Options &portopts, const char *name) {
+ const T *res = nullptr;
+ for (auto &cap: caps) {
+ if (!opts_ok(cap.opts, opts))
+ continue;
+ if (!opts_ok(cap.portopts, portopts))
+ continue;
+ if (res)
+ log_error("%s:%d: duplicate %s cap.\n", filename.c_str(), line_number, name);
+ res = &cap.val;
+ }
+ return res;
+ }
+
+ std::vector<Options> make_opt_combinations(const dict<std::string, pool<Const>> &opts) {
+ std::vector<Options> res;
+ res.push_back(Options());
+ for (auto &it: opts) {
+ std::vector<Options> new_res;
+ for (auto &val: it.second) {
+ for (Options o: res) {
+ o[it.first] = val;
+ new_res.push_back(o);
+ }
+ }
+ res = new_res;
+ }
+ return res;
+ }
+
+ void compile_portgroup(Ram &cram, PortGroupDef &pdef, dict<std::string, int> &clk_ids, const dict<std::string, int> &port_ids, int orig_line) {
+ PortGroup grp;
+ grp.optional = find_single_cap(pdef.optional, cram.options, Options(), "optional");
+ grp.optional_rw = find_single_cap(pdef.optional_rw, cram.options, Options(), "optional_rw");
+ grp.names = pdef.names;
+ for (auto portopts: make_opt_combinations(pdef.portopts)) {
+ bool forbidden = false;
+ for (auto &fdef: ram.forbid) {
+ if (opts_ok(fdef.opts, cram.options) && opts_ok(fdef.portopts, portopts)) {
+ forbidden = true;
+ }
+ }
+ if (forbidden)
+ continue;
+ PortVariant var;
+ var.options = portopts;
+ var.kind = pdef.kind;
+ if (pdef.kind != PortKind::Ar) {
+ const ClockDef *cdef = find_single_cap(pdef.clock, cram.options, portopts, "clock");
+ if (!cdef)
+ log_error("%s:%d: missing clock capability.\n", filename.c_str(), orig_line);
+ var.clk_pol = cdef->kind;
+ if (cdef->name.empty()) {
+ var.clk_shared = -1;
+ } else {
+ auto it = clk_ids.find(cdef->name);
+ bool anyedge = cdef->kind == ClkPolKind::Anyedge;
+ if (it == clk_ids.end()) {
+ clk_ids[cdef->name] = var.clk_shared = GetSize(cram.shared_clocks);
+ RamClock clk;
+ clk.name = cdef->name;
+ clk.anyedge = anyedge;
+ cram.shared_clocks.push_back(clk);
+ } else {
+ var.clk_shared = it->second;
+ if (cram.shared_clocks[var.clk_shared].anyedge != anyedge) {
+ log_error("%s:%d: named clock \"%s\" used with both posedge/negedge and anyedge clocks.\n", filename.c_str(), orig_line, cdef->name.c_str());
+ }
+ }
+ }
+ var.clk_en = find_single_cap(pdef.clken, cram.options, portopts, "clken");
+ }
+ const PortWidthDef *wdef = find_single_cap(pdef.width, cram.options, portopts, "width");
+ if (wdef) {
+ if (cram.width_mode != WidthMode::PerPort)
+ log_error("%s:%d: per-port width doesn't make sense for tied dbits.\n", filename.c_str(), orig_line);
+ compile_widths(var, cram.dbits, *wdef);
+ } else {
+ var.width_tied = true;
+ var.min_wr_wide_log2 = 0;
+ var.min_rd_wide_log2 = 0;
+ var.max_wr_wide_log2 = GetSize(cram.dbits) - 1;
+ var.max_rd_wide_log2 = GetSize(cram.dbits) - 1;
+ }
+ if (pdef.kind == PortKind::Srsw || pdef.kind == PortKind::Sr) {
+ const RdWrKind *rdwr = find_single_cap(pdef.rdwr, cram.options, portopts, "rdwr");
+ var.rdwr = rdwr ? *rdwr : RdWrKind::Undefined;
+ var.rd_en = find_single_cap(pdef.rden, cram.options, portopts, "rden");
+ const ResetValKind *iv = find_single_cap(pdef.rdinit, cram.options, portopts, "rdinit");
+ var.rdinitval = iv ? *iv : ResetValKind::None;
+ const ResetValKind *arv = find_single_cap(pdef.rdarst, cram.options, portopts, "rdarst");
+ var.rdarstval = arv ? *arv : ResetValKind::None;
+ const SrstDef *srv = find_single_cap(pdef.rdsrst, cram.options, portopts, "rdsrst");
+ if (srv) {
+ var.rdsrstval = srv->val;
+ var.rdsrstmode = srv->kind;
+ var.rdsrst_block_wr = srv->block_wr;
+ if (srv->kind == SrstKind::GatedClkEn && !var.clk_en)
+ log_error("%s:%d: `gated_clken` used without `clken`.\n", filename.c_str(), orig_line);
+ if (srv->kind == SrstKind::GatedRdEn && !var.rd_en)
+ log_error("%s:%d: `gated_rden` used without `rden`.\n", filename.c_str(), orig_line);
+ } else {
+ var.rdsrstval = ResetValKind::None;
+ var.rdsrstmode = SrstKind::None;
+ var.rdsrst_block_wr = false;
+ }
+ if (var.rdarstval == ResetValKind::Init || var.rdsrstval == ResetValKind::Init) {
+ if (var.rdinitval != ResetValKind::Any && var.rdinitval != ResetValKind::NoUndef) {
+ log_error("%s:%d: reset value `init` has to be paired with `any` or `no_undef` initial value.\n", filename.c_str(), orig_line);
+ }
+ }
+ }
+ var.wrbe_separate = find_single_cap(pdef.wrbe_separate, cram.options, portopts, "wrbe_separate");
+ if (var.wrbe_separate && cram.byte == 0) {
+ log_error("%s:%d: `wrbe_separate` used without `byte`.\n", filename.c_str(), orig_line);
+ }
+ for (auto &def: pdef.wrprio) {
+ if (!opts_ok(def.opts, cram.options))
+ continue;
+ if (!opts_ok(def.portopts, portopts))
+ continue;
+ var.wrprio.push_back(port_ids.at(def.val));
+ }
+ for (auto &def: pdef.wrtrans) {
+ if (!opts_ok(def.opts, cram.options))
+ continue;
+ if (!opts_ok(def.portopts, portopts))
+ continue;
+ WrTransDef tdef;
+ tdef.target_kind = def.val.target_kind;
+ if (def.val.target_kind == WrTransTargetKind::Group)
+ tdef.target_group = port_ids.at(def.val.target_group);
+ tdef.kind = def.val.kind;
+ var.wrtrans.push_back(tdef);
+ }
+ grp.variants.push_back(var);
+ }
+ if (grp.variants.empty()) {
+ log_error("%s:%d: all port option combinations are forbidden.\n", filename.c_str(), orig_line);
+ }
+ cram.port_groups.push_back(grp);
+ }
+
+ void compile_ram(int orig_line) {
+ if (ram.abits.empty())
+ log_error("%s:%d: `dims` capability should be specified.\n", filename.c_str(), orig_line);
+ if (ram.widths.empty())
+ log_error("%s:%d: `widths` capability should be specified.\n", filename.c_str(), orig_line);
+ if (ram.ports.empty())
+ log_error("%s:%d: at least one port group should be specified.\n", filename.c_str(), orig_line);
+ for (auto opts: make_opt_combinations(ram.opts)) {
+ bool forbidden = false;
+ for (auto &fdef: ram.forbid) {
+ if (opts_ok(fdef.opts, opts)) {
+ forbidden = true;
+ }
+ }
+ if (forbidden)
+ continue;
+ Ram cram;
+ cram.id = ram.id;
+ cram.kind = ram.kind;
+ cram.options = opts;
+ cram.prune_rom = find_single_cap(ram.prune_rom, opts, Options(), "prune_rom");
+ const int *abits = find_single_cap(ram.abits, opts, Options(), "abits");
+ if (!abits)
+ continue;
+ cram.abits = *abits;
+ const WidthsDef *widths = find_single_cap(ram.widths, opts, Options(), "widths");
+ if (!widths)
+ continue;
+ cram.dbits = widths->widths;
+ cram.width_mode = widths->mode;
+ const ResourceDef *resource = find_single_cap(ram.resource, opts, Options(), "resource");
+ if (resource) {
+ cram.resource_name = resource->name;
+ cram.resource_count = resource->count;
+ } else {
+ cram.resource_count = 1;
+ }
+ cram.cost = 0;
+ for (auto &cap: ram.cost) {
+ if (opts_ok(cap.opts, opts))
+ cram.cost += cap.val;
+ }
+ const double *widthscale = find_single_cap(ram.widthscale, opts, Options(), "widthscale");
+ if (widthscale)
+ cram.widthscale = *widthscale ? *widthscale : cram.cost;
+ else
+ cram.widthscale = 0;
+ const int *byte = find_single_cap(ram.byte, opts, Options(), "byte");
+ cram.byte = byte ? *byte : 0;
+ if (GetSize(cram.dbits) - 1 > cram.abits)
+ log_error("%s:%d: abits %d too small for dbits progression.\n", filename.c_str(), line_number, cram.abits);
+ validate_byte(widths->widths, cram.byte);
+ const MemoryInitKind *ik = find_single_cap(ram.init, opts, Options(), "init");
+ cram.init = ik ? *ik : MemoryInitKind::None;
+ for (auto &sdef: ram.style)
+ if (opts_ok(sdef.opts, opts))
+ cram.style.push_back(sdef.val);
+ dict<std::string, int> port_ids;
+ int ctr = 0;
+ for (auto &pdef: ram.ports) {
+ if (!opts_ok(pdef.opts, opts))
+ continue;
+ for (auto &name: pdef.val.names)
+ port_ids[name] = ctr;
+ ctr++;
+ }
+ dict<std::string, int> clk_ids;
+ for (auto &pdef: ram.ports) {
+ if (!opts_ok(pdef.opts, opts))
+ continue;
+ compile_portgroup(cram, pdef.val, clk_ids, port_ids, orig_line);
+ }
+ lib.rams.push_back(cram);
+ }
+ }
+
+ void validate_byte(const std::vector<int> &widths, int byte) {
+ if (byte == 0)
+ return;
+ if (byte >= widths.back())
+ return;
+ if (widths[0] % byte == 0) {
+ for (int j = 1; j < GetSize(widths); j++)
+ if (widths[j] % byte != 0)
+ log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte);
+ return;
+ }
+ for (int i = 0; i < GetSize(widths); i++) {
+ if (widths[i] == byte) {
+ for (int j = i + 1; j < GetSize(widths); j++)
+ if (widths[j] % byte != 0)
+ log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte);
+ return;
+ }
+ }
+ log_error("%s:%d: byte width %d invalid for dbits.\n", filename.c_str(), line_number, byte);
+ }
+
+ void compile_widths(PortVariant &var, const std::vector<int> &widths, const PortWidthDef &width) {
+ var.width_tied = width.tied;
+ auto wr_widths = compile_widthdef(widths, width.wr_widths);
+ var.min_wr_wide_log2 = wr_widths.first;
+ var.max_wr_wide_log2 = wr_widths.second;
+ if (width.tied) {
+ var.min_rd_wide_log2 = wr_widths.first;
+ var.max_rd_wide_log2 = wr_widths.second;
+ } else {
+ auto rd_widths = compile_widthdef(widths, width.rd_widths);
+ var.min_rd_wide_log2 = rd_widths.first;
+ var.max_rd_wide_log2 = rd_widths.second;
+ }
+ }
+
+ std::pair<int, int> compile_widthdef(const std::vector<int> &dbits, const std::vector<int> &widths) {
+ if (widths.empty())
+ return {0, GetSize(dbits) - 1};
+ for (int i = 0; i < GetSize(dbits); i++) {
+ if (dbits[i] == widths[0]) {
+ for (int j = 0; j < GetSize(widths); j++) {
+ if (i+j >= GetSize(dbits) || dbits[i+j] != widths[j]) {
+ log_error("%s:%d: port width %d doesn't match dbits progression.\n", filename.c_str(), line_number, widths[j]);
+ }
+ }
+ return {i, i + GetSize(widths) - 1};
+ }
+ }
+ log_error("%s:%d: port width %d invalid for dbits.\n", filename.c_str(), line_number, widths[0]);
+ }
+
+ void parse() {
+ while (peek_token() != "")
+ parse_top_item();
+ }
+};
+
+PRIVATE_NAMESPACE_END
+
+Library MemLibrary::parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines) {
+ Library res;
+ pool<std::string> defines_unused = defines;
+ for (auto &file: filenames) {
+ Parser(file, res, defines, defines_unused);
+ }
+ for (auto def: defines_unused) {
+ log_warning("define %s not used in the library.\n", def.c_str());
+ }
+ return res;
+}
diff --git a/passes/memory/memlib.h b/passes/memory/memlib.h
new file mode 100644
index 000000000..c3f7728f1
--- /dev/null
+++ b/passes/memory/memlib.h
@@ -0,0 +1,171 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2021 Marcelina Koƛcielnicka <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.
+ *
+ */
+
+#ifndef MEMLIB_H
+#define MEMLIB_H
+
+#include <string>
+#include <vector>
+
+#include "kernel/yosys.h"
+
+YOSYS_NAMESPACE_BEGIN
+
+namespace MemLibrary {
+
+enum class RamKind {
+ Auto,
+ Logic,
+ NotLogic,
+ Distributed,
+ Block,
+ Huge,
+};
+
+enum class WidthMode {
+ Single,
+ Global,
+ PerPort,
+};
+
+enum class MemoryInitKind {
+ None,
+ Zero,
+ Any,
+ NoUndef,
+};
+
+enum class PortKind {
+ Sr,
+ Ar,
+ Sw,
+ Srsw,
+ Arsw,
+};
+
+enum class ClkPolKind {
+ Anyedge,
+ Posedge,
+ Negedge,
+};
+
+enum class RdWrKind {
+ Undefined,
+ NoChange,
+ New,
+ Old,
+ NewOnly,
+};
+
+enum class ResetValKind {
+ None,
+ Zero,
+ Any,
+ NoUndef,
+ Init,
+};
+
+enum class SrstKind {
+ None,
+ Ungated,
+ GatedClkEn,
+ GatedRdEn,
+};
+
+enum class WrTransTargetKind {
+ All,
+ Group,
+};
+
+enum class WrTransKind {
+ New,
+ Old,
+};
+
+struct WrTransDef {
+ WrTransTargetKind target_kind;
+ int target_group;
+ WrTransKind kind;
+};
+
+struct PortVariant {
+ dict<std::string, Const> options;
+ PortKind kind;
+ int clk_shared;
+ ClkPolKind clk_pol;
+ bool clk_en;
+ bool width_tied;
+ int min_wr_wide_log2;
+ int max_wr_wide_log2;
+ int min_rd_wide_log2;
+ int max_rd_wide_log2;
+ bool rd_en;
+ RdWrKind rdwr;
+ ResetValKind rdinitval;
+ ResetValKind rdarstval;
+ ResetValKind rdsrstval;
+ SrstKind rdsrstmode;
+ bool rdsrst_block_wr;
+ bool wrbe_separate;
+ std::vector<int> wrprio;
+ std::vector<WrTransDef> wrtrans;
+};
+
+struct PortGroup {
+ bool optional;
+ bool optional_rw;
+ std::vector<std::string> names;
+ std::vector<PortVariant> variants;
+};
+
+struct RamClock {
+ std::string name;
+ bool anyedge;
+};
+
+struct Ram {
+ IdString id;
+ RamKind kind;
+ dict<std::string, Const> options;
+ std::vector<PortGroup> port_groups;
+ bool prune_rom;
+ int abits;
+ std::vector<int> dbits;
+ WidthMode width_mode;
+ std::string resource_name;
+ int resource_count;
+ double cost;
+ double widthscale;
+ int byte;
+ MemoryInitKind init;
+ std::vector<std::string> style;
+ std::vector<RamClock> shared_clocks;
+};
+
+struct Library {
+ std::vector<Ram> rams;
+};
+
+Library parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines);
+
+}
+
+YOSYS_NAMESPACE_END
+
+#endif
diff --git a/passes/memory/memlib.md b/passes/memory/memlib.md
new file mode 100644
index 000000000..fdc2d4bed
--- /dev/null
+++ b/passes/memory/memlib.md
@@ -0,0 +1,505 @@
+# The `memory_libmap` pass
+
+The `memory_libmap` pass is used to map memories to hardware primitives. To work,
+it needs a description of available target memories in a custom format.
+
+
+## Basic structure
+
+A basic library could look like this:
+
+ # A distributed-class RAM called $__RAM16X4SDP_
+ ram distributed $__RAM16X4SDP_ {
+ # Has 4 address bits (ie. 16 rows).
+ abits 4;
+ # Has 4 data bits.
+ width 4;
+ # Cost for the selection heuristic.
+ cost 4;
+ # Can be initialized to any value on startup.
+ init any;
+ # Has a synchronous write port called "W"...
+ port sw "W" {
+ # ... with a positive edge clock.
+ clock posedge;
+ }
+ # Has an asynchronous read port called "R".
+ port ar "R" {
+ }
+ }
+
+ # A block-class RAM called $__RAMB9K_
+ ram block $__RAMB9K_ {
+ # Has 13 address bits in the base (most narrow) data width.
+ abits 13;
+ # The available widths are:
+ # - 1 (13 address bits)
+ # - 2 (12 address bits)
+ # - 4 (11 address bits)
+ # - 9 (10 address bits)
+ # - 18 (9 address bits)
+ # The width selection is per-port.
+ widths 1 2 4 9 18 per_port;
+ # Has a write enable signal with 1 bit for every 9 data bits.
+ byte 9;
+ cost 64;
+ init any;
+ # Has two synchronous read+write ports, called "A" and "B".
+ port srsw "A" "B" {
+ clock posedge;
+ # Has a clock enable signal (gates both read and write).
+ clken;
+ # Has three per-port selectable options for handling read+write behavior:
+ portoption "RDWR" "NO_CHANGE" {
+ # When port is writing, reading is not done (output register keeps
+ # its value).
+ rdwr no_change;
+ }
+ portoption "RDWR" "OLD" {
+ # When port is writing, the data read is the old value (before the
+ # write).
+ rdwr old;
+ }
+ portoption "RDWR" "NEW" {
+ # When port is writing, the data read is the new value.
+ rdwr new;
+ }
+ }
+ }
+
+The pass will automatically select between the two available cells and
+the logic fallback (mapping the whole memory to LUTs+FFs) based on required
+capabilities and cost function. The selected memories will be transformed
+to intermediate `$__RAM16X4SDP_` and `$__RAMB9K_` cells that need to be mapped
+to actual hardware cells by a `techmap` pass, while memories selected for logic
+fallback will be left unmapped and will be later mopped up by `memory_map` pass.
+
+## RAM definition blocks
+
+The syntax for a RAM definition is:
+
+ ram <kind: distributed|block|huge> <name> {
+ <ram properties>
+ <ports>
+ }
+
+The `<name>` is used as the type of the mapped cell that will be passed to `techmap`.
+The memory kind is one of `distributed`, `block`, or `huge`. It describes the general
+class of the memory and can be matched on by manual selection attributes.
+
+The available ram properties are:
+
+- `abits <address bits>;`
+- `width <width>;`
+- `widths <width 1> <width 2> ... <width n> <global|per_port>;`
+- `byte <width>;`
+- `cost <cost>;`
+- `widthscale [<factor>];`
+- `resource <name> <count>;`
+- `init <none|zero|any|no_undef>;`
+- `style "<name 1>" "<name 2>" "<name 3>" ...;`
+- `prune_rom;`
+
+### RAM dimensions
+
+The memory dimensions are described by `abits` and `width` or `widths` properties.
+
+For a simple memory cell with a fixed width, use `abits` and `width` like this:
+
+ abits 4;
+ width 4;
+
+This will result in a `2**abits × width` memory cell.
+
+Multiple-width memories are also possible, and use the `widths` property instead.
+The rules for multiple-width memories are:
+
+- the widths are given in `widths` property in increasing order
+- the value in the `abits` property corresponds to the most narrow width
+- every width in the list needs to be greater than or equal to twice
+ the previous width (ie. `1 2 4 9 18` is valid, `1 2 4 7 14` is not)
+- it is assumed that, for every width in progression, the word in memory
+ is made of two smaller words, plus optionally some extra bits (eg. in the above
+ list, the 9-bit word is made of two 4-bit words and 1 extra bit), and thus
+ each sequential width in the list corresponds to one fewer usable address bit
+- all addresses connected to memory ports are always `abits` bits wide, with const
+ zero wired to the unused bits corresponding to wide ports
+
+When multiple widths are specified, they can be `per_port` or `global`.
+For the `global` version, the pass has to pick one width for the whole cell,
+and it is set on the resulting cell as the `WIDTH` parameter. For the `per_port`
+version, the selection is made on per-port basis, and passed using `PORT_*_WIDTH`
+parameters. When the mode is `per_port`, the width selection can be fine-tuned
+with the port `width` property.
+
+Specifying dimensions is mandatory.
+
+
+### Byte width
+
+If the memory cell has per-byte write enables, the `byte` property can be used
+to define the byte size (ie. how many data bits correspond to one write enable
+bit).
+
+The property is optional. If not used, it is assumed that there is a single
+write enable signal for each writable port.
+
+The rules for this property are as follows:
+
+- for every available width, the width needs to be a multiple of the byte size,
+ or the byte size needs to be larger than the width
+- if the byte size is larger than the width, the byte enable signel is assumed
+ to be one bit wide and cover the whole port
+- otherwise, the byte enable signal has one bit for every `byte` bits of the
+ data port
+
+The exact kind of byte enable signal is determined by the presence or absence
+of the per-port `wrbe_separate` property.
+
+
+### Cost properties
+
+The `cost` property is used to estimate the cost of using a given mapping.
+This is the cost of using one cell, and will be scaled as appropriate if
+the mapping requires multiple cells.
+
+If the `widthscale` property is specified, the mapping is assumed to be flexible,
+with cost scaling with the percentage of data width actually used. The value
+of the `widthscale` property is how much of the cost is scalable as such.
+If the value is omitted, all of the cost is assumed to scale.
+Eg. for the following properties:
+
+ width 14;
+ cost 8;
+ widthscale 7;
+
+The cost of a given cell will be assumed to be `(8 - 7) + 7 * (used_bits / 14)`.
+
+If `widthscale` is used, The pass will attach a `BITS_USED` parameter to mapped
+calls, with a bitmask of which data bits of the memory are actually in use.
+The parameter width will be the widest width in the `widths` property, and
+the bit correspondence is defined accordingly.
+
+The `cost` property is mandatory.
+
+
+### `init` property
+
+This property describes the state of the memory at initialization time. Can have
+one of the following values:
+
+- `none`: the memory contents are unpredictable, memories requiring any sort
+ of initialization will not be mapped to this cell
+- `zero`: the memory contents are zero, memories can be mapped to this cell iff
+ their initialization value is entirely zero or undef
+- `any`: the memory contents can be arbitrarily selected, and the initialization
+ will be passes as the `INIT` parameter to the mapped cell
+- `no_undef`: like `any`, but only 0 and 1 bit values are supported (the pass will
+ convert any x bits to 0)
+
+The `INIT` parameter is always constructed as a concatenation of words corresponding
+to the widest available `widths` setting, so that all available memory cell bits
+are covered.
+
+This property is optional and assumed to be `none` when not present.
+
+
+### `style` property
+
+Provides a name (or names) for this definition that can be passed to the `ram_style`
+or similar attribute to manually select it. Optional and can be used multiple times.
+
+
+### `prune_rom` property
+
+Specifying this property disqualifies the definition from consideration for source
+memories that have no write ports (ie. ROMs). Use this on definitions that have
+an obviously superior read-only alternative (eg. LUTRAMs) to make the pass skip
+them over quickly.
+
+
+## Port definition blocks
+
+The syntax for a port group definition is:
+
+ port <ar|sr|sw|arsw|srsw> "NAME 1" "NAME 2" ... {
+ <port properties>
+ }
+
+A port group definition defines a group of ports with identical properties.
+There are as many ports in a group as there are names given.
+
+Ports come in 5 kinds:
+
+- `ar`: asynchronous read port
+- `sr`: synchronous read port
+- `sw`: synchronous write port
+- `arsw`: simultanous synchronous write + asynchronous read with common address (commonly found in LUT RAMs)
+- `srsw`: synchronous write + synchronous read with common address
+
+The port properties available are:
+
+- `width <tied|mix>;`
+- `width <width 1> <width 2> ...;`
+- `width <tied|mix> <width 1> <width 2> ...;`
+- `width rd <width 1> <width 2> ... wr <width 1> <width 2> ...;`
+- `clock <posedge|negedge|anyedge> ["SHARED_NAME"];`
+- `clken;`
+- `rden;`
+- `wrbe_separate;`
+- `rdwr <undefined|no_change|new|old|new_only>;`
+- `rdinit <none|zero|any|no_undef>;`
+- `rdarst <none|zero|any|no_undef|init>;`
+- `rdsrst <none|zero|any|no_undef|init> <ungated|gatec_clken|gated_rden> [block_wr];`
+- `wrprio "NAME" "NAME" ...;`
+- `wrtrans <"NAME"|all> <old|new>;`
+- `optional;`
+- `optional_rw;`
+
+The base signals connected to the mapped cell for ports are:
+
+- `PORT_<name>_ADDR`: the address
+- `PORT_<name>_WR_DATA`: the write data (for `sw`/`arsw`/`srsw` ports only)
+- `PORT_<name>_RD_DATA`: the read data (for `ar`/`sr`/`arsw`/`srsw` ports only)
+- `PORT_<name>_WR_EN`: the write enable or enables (for `sw`/`arsw`/`srsw` ports only)
+
+The address is always `abits` wide. If a non-narrowest width is used, the appropriate low
+bits will be tied to 0.
+
+
+### Port `width` prooperty
+
+If the RAM has `per_port` widths, the available width selection can be further described
+on per-port basis, by using one of the following properties:
+
+- `width tied;`: any width from the master `widths` list is acceptable, and
+ (for read+write ports) the read and write width has to be the same
+- `width tied <width 1> <width 2> ...;`: like above, but limits the width
+ selection to the given list; the list has to be a contiguous sublist of the
+ master `widths` list
+- `width <width 1> <width 2> ...;`: alias for the above, to be used for read-only
+ or write-only ports
+- `width mix;`: any width from the master `widths` list is acceptable, and
+ read width can be different than write width (only usable for read+write ports)
+- `width mix <width 1> <width 2> ...;`: like above, but limits the width
+ selection to the given list; the list has to be a contiguous sublist of the
+ master `widths` list
+- `width rd <width 1> <width 2> ... wr <width 1> <width 2> ...;`: like above,
+ but the limitted selection can be different for read and write widths
+
+If `per_port` widths are in use and this property is not specified, `width tied;` is assumed.
+
+The parameters attached to the cell in `per_port` widths mode are:
+
+- `PORT_<name>_WIDTH`: the selected width (for `tied` ports)
+- `PORT_<name>_RD_WIDTH`: the selected read width (for `mix` ports)
+- `PORT_<name>_WR_WIDTH`: the selected write width (for `mix` ports)
+
+
+### `clock` property
+
+The `clock` property is used with synchronous ports (and synchronous ports only).
+It is mandatory for them and describes the clock polarity and clock sharing.
+`anyedge` means that both polarities are supported.
+
+If a shared clock name is provided, the port is assumed to have a shared clock signal
+with all other ports using the same shared name. Otherwise, the port is assumed to
+have its own clock signal.
+
+The port clock is always provided on the memory cell as `PORT_<name>_CLK` signal
+(even if it is also shared). Shared clocks are also provided as `CLK_<shared_name>`
+signals.
+
+For `anyedge` clocks, the cell gets a `PORT_<name>_CLKPOL` parameter that is set
+to 1 for `posedge` clocks and 0 for `negedge` clocks. If the clock is shared,
+the same information will also be provided as `CLK_<shared_name>_POL` parameter.
+
+
+### `clken` and `rden`
+
+The `clken` property, if present, means that the port has a clock enable signal
+gating both reads and writes. Such signal will be provided to the mapped cell
+as `PORT_<name>_CLK_EN`. It is only applicable to synchronous ports.
+
+The `rden` property, if present, means that the port has a read clock enable signal.
+Such signal will be provided to the mapped cell as `PORT_<name>_RD_EN`. It is only
+applicable to synchronous read ports (`sr` and `srsw`).
+
+For `sr` ports, both of these options are effectively equivalent.
+
+
+### `wrbe_separate` and the write enables
+
+The `wrbe_separate` property specifies that the write byte enables are provided
+as a separate signal from the main write enable. It can only be used when the
+RAM-level `byte` property is also specified.
+
+The rules are as follows:
+
+If no `byte` is specified:
+
+- `wrbe_separate` is not allowed
+- `PORT_<name>_WR_EN` signal is single bit
+
+If `byte` is specified, but `wrbe_separate` is not:
+
+- `PORT_<name>_WR_EN` signal has one bit for every data byte
+- `PORT_<name>_WR_EN_WIDTH` parameter is the width of the above (only present for multiple-width cells)
+
+If `byte` is specified and `wrbe_separate` is present:
+
+- `PORT_<name>_WR_EN` signal is single bit
+- `PORT_<name>_WR_BE` signal has one bit for every data byte
+- `PORT_<name>_WR_BE_WIDTH` parameter is the width of the above (only present for multiple-width cells)
+- a given byte is written iff all of `CLK_EN` (if present), `WR_EN`, and the corresponding `WR_BE` bit are one
+
+This property can only be used on write ports.
+
+
+### `rdwr` property
+
+This property is allowed only on `srsw` ports and describes read-write interactions.
+
+The possible values are:
+
+- `no_change`: if write is being performed (any bit of `WR_EN` is set),
+ reading is not performed and the `RD_DATA` keeps its old value
+- `undefined`: all `RD_DATA` bits corresponding to enabled `WR_DATA` bits
+ have undefined value, remaining bits read from memory
+- `old`: all `RD_DATA` bits get the previous value in memory
+- `new`: all `RD_DATA` bits get the new value in memory (transparent write)
+- `new_only`: all `RD_DATA` bits corresponding to enabled `WR_DATA` bits
+ get the new value, all others are undefined
+
+If this property is not found on an `srsw` port, `undefined` is assumed.
+
+
+### Read data initial value and resets
+
+The `rdinit`, `rdarst`, and `rdsrst` are applicable only to synchronous read
+ports.
+
+`rdinit` describes the initial value of the read port data, and can be set to
+one of the following:
+
+- `none`: initial data is indeterminate
+- `zero`: initial data is all-0
+- `any`: initial data is arbitrarily configurable, and the selected value
+ will be attached to the cell as `PORT_<name>_RD_INIT_VALUE` parameter
+- `no_undef`: like `any`, but only 0 and 1 bits are allowed
+
+`rdarst` and `rdsrst` describe the asynchronous and synchronous reset capabilities.
+The values are similar to `rdinit`:
+
+- `none`: no reset
+- `zero`: reset to all-0 data
+- `any`: reset to arbitrary value, the selected value
+ will be attached to the cell as `PORT_<name>_RD_ARST_VALUE` or
+ `PORT_<name>_RD_SRST_VALUE` parameter
+- `no_undef`: like `any`, but only 0 and 1 bits are allowed
+- `init`: reset to the initial value, as specified by `rdinit` (which must be `any`
+ or `no_undef` itself)
+
+If the capability is anything other than `none`, the reset signal
+will be provided as `PORT_<name>_RD_ARST` or `PORT_<name>_RD_SRST`.
+
+For `rdsrst`, the priority must be additionally specified, as one of:
+
+- `ungated`: `RD_SRST` has priority over both `CLK_EN` and `RD_EN` (if present)
+- `gated_clken`: `CLK_EN` has priority over `RD_SRST`; `RD_SRST` has priority over `RD_EN` if present
+- `gated_rden`: `RD_EN` and `CLK_EN` (if present) both have priority over `RD_SRST`
+
+Also, `rdsrst` can optionally have `block_wr` specified, which means that sync reset
+cannot be performed in the same cycle as a write.
+
+If not provided, `none` is assumed for all three properties.
+
+
+### Write priority
+
+The `wrprio` property is only allowed on write ports and defines a priority relationship
+between port — when `wrprio "B";` is used in definition of port `"A"`, and both ports
+simultanously write to the same memory cell, the value written by port `"A"` will have
+precedence.
+
+This property is optional, and can be used multiple times as necessary. If no relationship
+is described for a pair of write ports, no priority will be assumed.
+
+
+### Write transparency
+
+The `wrtrans` property is only allowed on write ports and defines behavior when
+another synchronous read port reads from the memory cell at the same time as the
+given port writes it. The values are:
+
+- `old`: the read port will get the old value of the cell
+- `new`: the read port will get the new value of the cell
+
+This property is optional, and can be used multiple times as necessary. If no relationship
+is described for a pair of ports, the value read is assumed to be indeterminate.
+
+Note that this property is not used to describe the read value on the port itself for `srsw`
+ports — for that purpose, the `rdwr` property is used instead.
+
+
+### Optional ports
+
+The `optional;` property will make the pass attach a `PORT_<name>_USED` parameter
+with a boolean value specifying whether a given port was meaningfully used in
+mapping a given cell. Likewise, `optional_rw;` will attach `PORT_<name>_RD_USED`
+and `PORT_<name>_WR_USED` the specify whether the read / write part in particular
+was used. These can be useful if the mapping has some meaningful optimization
+to apply for unused ports, but doesn't otherwise influence the selection process.
+
+
+## Options
+
+For highly configurable cells, multiple variants may be described in one cell description.
+All properties and port definitions within a RAM or port definition can be put inside
+an `option` block as follows:
+
+ option "NAME" <value> {
+ <properties, ports, ...>
+ }
+
+The value and name of an option are arbitrary, and the selected option value
+will be provided to the cell as `OPTION_<name>` parameter. Values can be
+strings or integers.
+
+
+Likewise, for per-port options, a `portoption` block can be used:
+
+ portoption "NAME" <value> {
+ <properties, ...>
+ }
+
+These options will be provided as `PORT_<pname>_OPTION_<oname>` parameters.
+
+The library parser will simply expand the RAM definition for every possible combination
+of option values mentioned in the RAM body, and likewise for port definitions.
+This can lead to a combinatorial explosion.
+
+If some option values cannot be used together, a `forbid` pseudo-property can be used
+to discard a given combination, eg:
+
+ option "ABC" 1 {
+ portoption "DEF" "GHI" {
+ forbid;
+ }
+ }
+
+will disallow combining the RAM option `ABC = 2` with port option `DEF = "GHI"`.
+
+
+## Ifdefs
+
+To allow reusing a library for multiple FPGA families with slighly differing
+capabilities, `ifdef` (and `ifndef`) blocks are provided:
+
+ ifdef IS_FANCY_FPGA_WITH_CONFIGURABLE_ASYNC_RESET {
+ rdarst any;
+ } else {
+ rdarst zero;
+ }
+
+Such blocks can be enabled by passing the `-D` option to the pass.
diff --git a/passes/memory/memory.cc b/passes/memory/memory.cc
index bac547c1a..d5dec6198 100644
--- a/passes/memory/memory.cc
+++ b/passes/memory/memory.cc
@@ -31,14 +31,15 @@ struct MemoryPass : public Pass {
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
- log(" memory [-nomap] [-nordff] [-nowiden] [-nosat] [-memx] [-bram <bram_rules>] [selection]\n");
+ log(" memory [-norom] [-nomap] [-nordff] [-nowiden] [-nosat] [-memx] [-no-rw-check] [-bram <bram_rules>] [selection]\n");
log("\n");
log("This pass calls all the other memory_* passes in a useful order:\n");
log("\n");
log(" opt_mem\n");
log(" opt_mem_priority\n");
log(" opt_mem_feedback\n");
- log(" memory_dff (skipped if called with -nordff or -memx)\n");
+ log(" memory_bmux2rom (skipped if called with -norom)\n");
+ log(" memory_dff [-no-rw-check] (skipped if called with -nordff or -memx)\n");
log(" opt_clean\n");
log(" memory_share [-nowiden] [-nosat]\n");
log(" opt_mem_widen\n");
@@ -54,9 +55,11 @@ struct MemoryPass : public Pass {
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
+ bool flag_norom = false;
bool flag_nomap = false;
bool flag_nordff = false;
bool flag_memx = false;
+ string memory_dff_opts;
string memory_bram_opts;
string memory_share_opts;
@@ -65,6 +68,10 @@ struct MemoryPass : public Pass {
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-norom") {
+ flag_norom = true;
+ continue;
+ }
if (args[argidx] == "-nomap") {
flag_nomap = true;
continue;
@@ -86,6 +93,10 @@ struct MemoryPass : public Pass {
memory_share_opts += " -nosat";
continue;
}
+ if (args[argidx] == "-no-rw-check") {
+ memory_dff_opts += " -no-rw-check";
+ continue;
+ }
if (argidx+1 < args.size() && args[argidx] == "-bram") {
memory_bram_opts += " -rules " + args[++argidx];
continue;
@@ -97,8 +108,10 @@ struct MemoryPass : public Pass {
Pass::call(design, "opt_mem");
Pass::call(design, "opt_mem_priority");
Pass::call(design, "opt_mem_feedback");
+ if (!flag_norom)
+ Pass::call(design, "memory_bmux2rom");
if (!flag_nordff)
- Pass::call(design, "memory_dff");
+ Pass::call(design, "memory_dff" + memory_dff_opts);
Pass::call(design, "opt_clean");
Pass::call(design, "memory_share" + memory_share_opts);
Pass::call(design, "opt_mem_widen");
diff --git a/passes/memory/memory_bmux2rom.cc b/passes/memory/memory_bmux2rom.cc
new file mode 100644
index 000000000..a3fc5a7fc
--- /dev/null
+++ b/passes/memory/memory_bmux2rom.cc
@@ -0,0 +1,87 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2022 Marcelina Koƛcielnicka <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"
+#include "kernel/mem.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct MemoryBmux2RomPass : public Pass {
+ MemoryBmux2RomPass() : Pass("memory_bmux2rom", "convert muxes to ROMs") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" memory_bmux2rom [options] [selection]\n");
+ log("\n");
+ log("This pass converts $bmux cells with constant A input to ROMs.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ log_header(design, "Executing MEMORY_BMUX2ROM pass (converting muxes to ROMs).\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules()) {
+ for (auto cell : module->selected_cells()) {
+ if (cell->type != ID($bmux))
+ continue;
+
+ SigSpec sig_a = cell->getPort(ID::A);
+ if (!sig_a.is_fully_const())
+ continue;
+
+ int abits = cell->getParam(ID::S_WIDTH).as_int();
+ int width = cell->getParam(ID::WIDTH).as_int();
+ if (abits < 3)
+ continue;
+
+ // Ok, let's do it.
+ Mem mem(module, NEW_ID, width, 0, 1 << abits);
+ mem.attributes = cell->attributes;
+
+ MemInit init;
+ init.addr = 0;
+ init.data = sig_a.as_const();
+ init.en = Const(State::S1, width);
+ mem.inits.push_back(std::move(init));
+
+ MemRd rd;
+ rd.addr = cell->getPort(ID::S);
+ rd.data = cell->getPort(ID::Y);
+ rd.init_value = Const(State::Sx, width);
+ rd.arst_value = Const(State::Sx, width);
+ rd.srst_value = Const(State::Sx, width);
+ mem.rd_ports.push_back(std::move(rd));
+
+ mem.emit();
+ module->remove(cell);
+ }
+ }
+ }
+} MemoryBmux2RomPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/memory/memory_bram.cc b/passes/memory/memory_bram.cc
index fed9d60c0..b1f45d5fc 100644
--- a/passes/memory/memory_bram.cc
+++ b/passes/memory/memory_bram.cc
@@ -644,22 +644,6 @@ grow_read_ports:;
log(" Bram port %c%d.%d has incompatible clock polarity.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
goto skip_bram_rport;
}
- if (port.en != State::S1 && pi.enable == 0) {
- log(" Bram port %c%d.%d has no read enable input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
- goto skip_bram_rport;
- }
- if (port.arst != State::S0) {
- log(" Bram port %c%d.%d has no async reset input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
- goto skip_bram_rport;
- }
- if (port.srst != State::S0) {
- log(" Bram port %c%d.%d has no sync reset input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
- goto skip_bram_rport;
- }
- if (!port.init_value.is_fully_undef()) {
- log(" Bram port %c%d.%d has no initial value support.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
- goto skip_bram_rport;
- }
if (non_transp && read_transp.count(pi.transp) && read_transp.at(pi.transp)) {
log(" Bram port %c%d.%d has incompatible read transparency.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
goto skip_bram_rport;
@@ -794,10 +778,18 @@ grow_read_ports:;
// Apply make_outreg and make_transp where necessary.
for (auto &pi : portinfos) {
- if (pi.make_outreg)
+ if (pi.mapped_port == -1 || pi.wrmode)
+ continue;
+ auto &port = mem.rd_ports[pi.mapped_port];
+ if (pi.make_outreg) {
mem.extract_rdff(pi.mapped_port, initvals);
+ } else if (port.clk_enable) {
+ if (!pi.enable && port.en != State::S1)
+ mem.emulate_rden(pi.mapped_port, initvals);
+ else
+ mem.emulate_reset(pi.mapped_port, true, true, true, initvals);
+ }
if (pi.make_transp) {
- auto &port = mem.rd_ports[pi.mapped_port];
for (int i = 0; i < GetSize(mem.wr_ports); i++)
if (port.transparency_mask[i])
mem.emulate_transparency(i, pi.mapped_port, initvals);
diff --git a/passes/memory/memory_dff.cc b/passes/memory/memory_dff.cc
index 91209d428..998e86491 100644
--- a/passes/memory/memory_dff.cc
+++ b/passes/memory/memory_dff.cc
@@ -220,8 +220,9 @@ struct MemoryDffWorker
ModWalker modwalker;
FfInitVals initvals;
FfMergeHelper merger;
+ bool flag_no_rw_check;
- MemoryDffWorker(Module *module) : module(module), modwalker(module->design)
+ MemoryDffWorker(Module *module, bool flag_no_rw_check) : module(module), modwalker(module->design), flag_no_rw_check(flag_no_rw_check)
{
modwalker.setup(module);
initvals.set(&modwalker.sigmap, module);
@@ -357,6 +358,14 @@ struct MemoryDffWorker
return;
}
+ // Check for no_rw_check
+ bool no_rw_check = flag_no_rw_check || mem.get_bool_attribute(ID::no_rw_check);
+ for (auto attr: {ID::ram_block, ID::rom_block, ID::ram_style, ID::rom_style, ID::ramstyle, ID::romstyle, ID::syn_ramstyle, ID::syn_romstyle}) {
+ if (mem.get_string_attribute(attr) == "no_rw_check") {
+ no_rw_check = true;
+ }
+ }
+
// Construct cache.
MemQueryCache cache(qcsat, mem, port, ff);
@@ -392,6 +401,8 @@ struct MemoryDffWorker
pd.uncollidable_mask[j] = true;
pd.collision_x_mask[j] = true;
}
+ if (no_rw_check)
+ pd.collision_x_mask[j] = true;
}
}
portdata.push_back(pd);
@@ -618,25 +629,35 @@ struct MemoryDffPass : public Pass {
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
- log(" memory_dff [options] [selection]\n");
+ log(" memory_dff [-no-rw-check] [selection]\n");
log("\n");
log("This pass detects DFFs at memory read ports and merges them into the memory port.\n");
log("I.e. it consumes an asynchronous memory port and the flip-flops at its\n");
log("interface and yields a synchronous memory port.\n");
log("\n");
+ log(" -no-rw-check\n");
+ log(" marks all recognized read ports as \"return don't-care value on\n");
+ log(" read/write collision\" (same result as setting the no_rw_check\n");
+ log(" attribute on all memories).\n");
+ log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
+ bool flag_no_rw_check = false;
log_header(design, "Executing MEMORY_DFF pass (merging $dff cells to $memrd).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-no-rw-check") {
+ flag_no_rw_check = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
for (auto mod : design->selected_modules()) {
- MemoryDffWorker worker(mod);
+ MemoryDffWorker worker(mod, flag_no_rw_check);
worker.run();
}
}
diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc
new file mode 100644
index 000000000..898e0af85
--- /dev/null
+++ b/passes/memory/memory_libmap.cc
@@ -0,0 +1,2096 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2021 Marcelina Koƛcielnicka <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 "memlib.h"
+
+#include <ctype.h>
+
+#include "kernel/yosys.h"
+#include "kernel/sigtools.h"
+#include "kernel/mem.h"
+#include "kernel/qcsat.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+using namespace MemLibrary;
+
+#define FACTOR_MUX 0.5
+#define FACTOR_DEMUX 0.5
+#define FACTOR_EMU 2
+
+struct PassOptions {
+ bool no_auto_distributed;
+ bool no_auto_block;
+ bool no_auto_huge;
+ double logic_cost_rom;
+ double logic_cost_ram;
+};
+
+struct WrPortConfig {
+ // Index of the read port this port is merged with, or -1 if none.
+ int rd_port;
+ // Index of the PortGroup in the Ram.
+ int port_group;
+ int port_variant;
+ const PortVariant *def;
+ // Emulate priority logic for this list of (source) write port indices.
+ std::vector<int> emu_prio;
+ // If true, this port needs to end up with uniform byte enables to work correctly.
+ bool force_uniform;
+
+ WrPortConfig() : rd_port(-1), force_uniform(false) {}
+};
+
+struct RdPortConfig {
+ // Index of the write port this port is merged with, or -1 if none.
+ int wr_port;
+ // Index of the PortGroup in the Ram.
+ int port_group;
+ int port_variant;
+ const PortVariant *def;
+ // If true, this is a sync port mapped into async mem, make an output
+ // register. Mutually exclusive with the following options.
+ bool emu_sync;
+ // Emulate the EN / ARST / SRST / init value circuitry.
+ bool emu_en;
+ bool emu_arst;
+ bool emu_srst;
+ bool emu_init;
+ // Emulate EN-SRST priority.
+ bool emu_srst_en_prio;
+ // If true, use clk_en as rd_en.
+ bool rd_en_to_clk_en;
+ // Emulate transparency logic for this list of (source) write port indices.
+ std::vector<int> emu_trans;
+
+ RdPortConfig() : wr_port(-1), emu_sync(false), emu_en(false), emu_arst(false), emu_srst(false), emu_init(false), emu_srst_en_prio(false), rd_en_to_clk_en(false) {}
+};
+
+// The named clock and clock polarity assignments.
+struct SharedClockConfig {
+ bool used;
+ SigBit clk;
+ // For anyedge clocks.
+ bool polarity;
+ // For non-anyedge clocks.
+ bool invert;
+};
+
+struct MemConfig {
+ // Reference to the library ram definition
+ const Ram *def;
+ // Port assignments, indexed by Mem port index.
+ std::vector<WrPortConfig> wr_ports;
+ std::vector<RdPortConfig> rd_ports;
+ std::vector<SharedClockConfig> shared_clocks;
+ // Emulate read-first write-read behavior using soft logic.
+ bool emu_read_first;
+ // This many low bits of (target) address are always-0 on all ports.
+ int base_width_log2;
+ int unit_width_log2;
+ std::vector<int> swizzle;
+ int hard_wide_mask;
+ int emu_wide_mask;
+ // How many times the base memory block will need to be duplicated to get more
+ // data bits.
+ int repl_d;
+ // How many times the whole memory array will need to be duplicated to cover
+ // all read ports required.
+ int repl_port;
+ // Emulation score — how much circuitry we need to add for priority / transparency /
+ // reset / initial value emulation.
+ int score_emu;
+ // Mux score — how much circuitry we need to add to manually decode whatever address
+ // bits are not decoded by the memory array itself, for reads.
+ int score_mux;
+ // Demux score — how much circuitry we need to add to manually decode whatever address
+ // bits are not decoded by the memory array itself, for writes.
+ int score_demux;
+ double cost;
+ MemConfig() : emu_read_first(false) {}
+};
+
+typedef std::vector<MemConfig> MemConfigs;
+
+struct MapWorker {
+ Module *module;
+ ModWalker modwalker;
+ SigMap sigmap;
+ SigMap sigmap_xmux;
+ FfInitVals initvals;
+
+ MapWorker(Module *module) : module(module), modwalker(module->design, module), sigmap(module), sigmap_xmux(module), initvals(&sigmap, module) {
+ for (auto cell : module->cells())
+ {
+ if (cell->type == ID($mux))
+ {
+ RTLIL::SigSpec sig_a = sigmap_xmux(cell->getPort(ID::A));
+ RTLIL::SigSpec sig_b = sigmap_xmux(cell->getPort(ID::B));
+
+ if (sig_a.is_fully_undef())
+ sigmap_xmux.add(cell->getPort(ID::Y), sig_b);
+ else if (sig_b.is_fully_undef())
+ sigmap_xmux.add(cell->getPort(ID::Y), sig_a);
+ }
+ }
+ }
+};
+
+struct SwizzleBit {
+ bool valid;
+ int mux_idx;
+ int addr;
+ int bit;
+};
+
+struct Swizzle {
+ int addr_shift;
+ int addr_start;
+ int addr_end;
+ std::vector<int> addr_mux_bits;
+ std::vector<std::vector<SwizzleBit>> bits;
+};
+
+struct MemMapping {
+ MapWorker &worker;
+ QuickConeSat qcsat;
+ Mem &mem;
+ const Library &lib;
+ const PassOptions &opts;
+ std::vector<MemConfig> cfgs;
+ bool logic_ok;
+ double logic_cost;
+ RamKind kind;
+ std::string style;
+ dict<int, int> wr_en_cache;
+ dict<std::pair<int, int>, bool> wr_implies_rd_cache;
+ dict<std::pair<int, int>, bool> wr_excludes_rd_cache;
+ dict<std::pair<int, int>, bool> wr_excludes_srst_cache;
+
+ MemMapping(MapWorker &worker, Mem &mem, const Library &lib, const PassOptions &opts) : worker(worker), qcsat(worker.modwalker), mem(mem), lib(lib), opts(opts) {
+ determine_style();
+ logic_ok = determine_logic_ok();
+ if (GetSize(mem.wr_ports) == 0)
+ logic_cost = mem.width * mem.size * opts.logic_cost_rom;
+ else
+ logic_cost = mem.width * mem.size * opts.logic_cost_ram;
+ if (kind == RamKind::Logic)
+ return;
+ for (int i = 0; i < GetSize(lib.rams); i++) {
+ auto &rdef = lib.rams[i];
+ if (!check_ram_kind(rdef))
+ continue;
+ if (!check_ram_style(rdef))
+ continue;
+ if (!check_init(rdef))
+ continue;
+ if (rdef.prune_rom && mem.wr_ports.empty())
+ continue;
+ MemConfig cfg;
+ cfg.def = &rdef;
+ for (auto &cdef: rdef.shared_clocks) {
+ (void)cdef;
+ SharedClockConfig clk;
+ clk.used = false;
+ cfg.shared_clocks.push_back(clk);
+ }
+ cfgs.push_back(cfg);
+ }
+ assign_wr_ports();
+ assign_rd_ports();
+ handle_trans();
+ // If we got this far, the memory is mappable. The following two can require emulating
+ // some functionality, but cannot cause the mapping to fail.
+ handle_priority();
+ handle_rd_rst();
+ score_emu_ports();
+ // Now it is just a matter of picking geometry.
+ handle_geom();
+ dump_configs(0);
+ prune_post_geom();
+ dump_configs(1);
+ }
+
+ bool addr_compatible(int wpidx, int rpidx) {
+ auto &wport = mem.wr_ports[wpidx];
+ auto &rport = mem.rd_ports[rpidx];
+ int max_wide_log2 = std::max(rport.wide_log2, wport.wide_log2);
+ SigSpec raddr = rport.addr.extract_end(max_wide_log2);
+ SigSpec waddr = wport.addr.extract_end(max_wide_log2);
+ int abits = std::max(GetSize(raddr), GetSize(waddr));
+ raddr.extend_u0(abits);
+ waddr.extend_u0(abits);
+ return worker.sigmap_xmux(raddr) == worker.sigmap_xmux(waddr);
+ }
+
+ int get_wr_en(int wpidx) {
+ auto it = wr_en_cache.find(wpidx);
+ if (it != wr_en_cache.end())
+ return it->second;
+ int res = qcsat.ez->expression(qcsat.ez->OpOr, qcsat.importSig(mem.wr_ports[wpidx].en));
+ wr_en_cache.insert({wpidx, res});
+ return res;
+ }
+
+ bool get_wr_implies_rd(int wpidx, int rpidx) {
+ auto key = std::make_pair(wpidx, rpidx);
+ auto it = wr_implies_rd_cache.find(key);
+ if (it != wr_implies_rd_cache.end())
+ return it->second;
+ int wr_en = get_wr_en(wpidx);
+ int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]);
+ qcsat.prepare();
+ bool res = !qcsat.ez->solve(wr_en, qcsat.ez->NOT(rd_en));
+ wr_implies_rd_cache.insert({key, res});
+ return res;
+ }
+
+ bool get_wr_excludes_rd(int wpidx, int rpidx) {
+ auto key = std::make_pair(wpidx, rpidx);
+ auto it = wr_excludes_rd_cache.find(key);
+ if (it != wr_excludes_rd_cache.end())
+ return it->second;
+ int wr_en = get_wr_en(wpidx);
+ int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]);
+ qcsat.prepare();
+ bool res = !qcsat.ez->solve(wr_en, rd_en);
+ wr_excludes_rd_cache.insert({key, res});
+ return res;
+ }
+
+ bool get_wr_excludes_srst(int wpidx, int rpidx) {
+ auto key = std::make_pair(wpidx, rpidx);
+ auto it = wr_excludes_srst_cache.find(key);
+ if (it != wr_excludes_srst_cache.end())
+ return it->second;
+ int wr_en = get_wr_en(wpidx);
+ int srst = qcsat.importSigBit(mem.rd_ports[rpidx].srst);
+ if (mem.rd_ports[rpidx].ce_over_srst) {
+ int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]);
+ srst = qcsat.ez->AND(srst, rd_en);
+ }
+ qcsat.prepare();
+ bool res = !qcsat.ez->solve(wr_en, srst);
+ wr_excludes_srst_cache.insert({key, res});
+ return res;
+ }
+
+ void dump_configs(int stage);
+ void dump_config(MemConfig &cfg);
+ void determine_style();
+ bool determine_logic_ok();
+ bool check_ram_kind(const Ram &ram);
+ bool check_ram_style(const Ram &ram);
+ bool check_init(const Ram &ram);
+ void assign_wr_ports();
+ void assign_rd_ports();
+ void handle_trans();
+ void handle_priority();
+ void handle_rd_rst();
+ void score_emu_ports();
+ void handle_geom();
+ void prune_post_geom();
+ void emit_port(const MemConfig &cfg, std::vector<Cell*> &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector<int> &hw_addr_swizzle);
+ void emit(const MemConfig &cfg);
+};
+
+void MemMapping::dump_configs(int stage) {
+ const char *stage_name;
+ switch (stage) {
+ case 0:
+ stage_name = "post-geometry";
+ break;
+ case 1:
+ stage_name = "after post-geometry prune";
+ break;
+ default:
+ abort();
+ }
+ log_debug("Memory %s.%s mapping candidates (%s):\n", log_id(mem.module->name), log_id(mem.memid), stage_name);
+ if (logic_ok) {
+ log_debug("- logic fallback\n");
+ log_debug(" - cost: %f\n", logic_cost);
+ }
+ for (auto &cfg: cfgs) {
+ dump_config(cfg);
+ }
+}
+
+void MemMapping::dump_config(MemConfig &cfg) {
+ log_debug("- %s:\n", log_id(cfg.def->id));
+ for (auto &it: cfg.def->options)
+ log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second));
+ log_debug(" - emulation score: %d\n", cfg.score_emu);
+ log_debug(" - replicates (for ports): %d\n", cfg.repl_port);
+ log_debug(" - replicates (for data): %d\n", cfg.repl_d);
+ log_debug(" - mux score: %d\n", cfg.score_mux);
+ log_debug(" - demux score: %d\n", cfg.score_demux);
+ log_debug(" - cost: %f\n", cfg.cost);
+ std::stringstream os;
+ for (int x: cfg.def->dbits)
+ os << " " << x;
+ std::string dbits_s = os.str();
+ log_debug(" - abits %d dbits%s\n", cfg.def->abits, dbits_s.c_str());
+ if (cfg.def->byte != 0)
+ log_debug(" - byte width %d\n", cfg.def->byte);
+ log_debug(" - chosen base width %d\n", cfg.def->dbits[cfg.base_width_log2]);
+ os.str("");
+ for (int x: cfg.swizzle)
+ if (x == -1)
+ os << " -";
+ else
+ os << " " << x;
+ std::string swizzle_s = os.str();
+ log_debug(" - swizzle%s\n", swizzle_s.c_str());
+ os.str("");
+ for (int i = 0; (1 << i) <= cfg.hard_wide_mask; i++)
+ if (cfg.hard_wide_mask & 1 << i)
+ os << " " << i;
+ std::string wide_s = os.str();
+ if (cfg.hard_wide_mask)
+ log_debug(" - hard wide bits%s\n", wide_s.c_str());
+ if (cfg.emu_read_first)
+ log_debug(" - emulate read-first behavior\n");
+ for (int i = 0; i < GetSize(mem.wr_ports); i++) {
+ auto &pcfg = cfg.wr_ports[i];
+ if (pcfg.rd_port == -1)
+ log_debug(" - write port %d: port group %s\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str());
+ else
+ log_debug(" - write port %d: port group %s (shared with read port %d)\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str(), pcfg.rd_port);
+
+ for (auto &it: pcfg.def->options)
+ log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second));
+ if (cfg.def->width_mode == WidthMode::PerPort) {
+ std::stringstream os;
+ for (int i = pcfg.def->min_wr_wide_log2; i <= pcfg.def->max_wr_wide_log2; i++)
+ os << " " << cfg.def->dbits[i];
+ std::string widths_s = os.str();
+ const char *note = "";
+ if (pcfg.rd_port != -1)
+ note = pcfg.def->width_tied ? " (tied)" : " (independent)";
+ log_debug(" - widths%s%s\n", widths_s.c_str(), note);
+ }
+ for (auto i: pcfg.emu_prio)
+ log_debug(" - emulate priority over write port %d\n", i);
+ }
+ for (int i = 0; i < GetSize(mem.rd_ports); i++) {
+ auto &pcfg = cfg.rd_ports[i];
+ if (pcfg.wr_port == -1)
+ log_debug(" - read port %d: port group %s\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str());
+ else
+ log_debug(" - read port %d: port group %s (shared with write port %d)\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str(), pcfg.wr_port);
+ for (auto &it: pcfg.def->options)
+ log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second));
+ if (cfg.def->width_mode == WidthMode::PerPort) {
+ std::stringstream os;
+ for (int i = pcfg.def->min_rd_wide_log2; i <= pcfg.def->max_rd_wide_log2; i++)
+ os << " " << cfg.def->dbits[i];
+ std::string widths_s = os.str();
+ const char *note = "";
+ if (pcfg.wr_port != -1)
+ note = pcfg.def->width_tied ? " (tied)" : " (independent)";
+ log_debug(" - widths%s%s\n", widths_s.c_str(), note);
+ }
+ if (pcfg.emu_sync)
+ log_debug(" - emulate data register\n");
+ if (pcfg.emu_en)
+ log_debug(" - emulate clock enable\n");
+ if (pcfg.emu_arst)
+ log_debug(" - emulate async reset\n");
+ if (pcfg.emu_srst)
+ log_debug(" - emulate sync reset\n");
+ if (pcfg.emu_init)
+ log_debug(" - emulate init value\n");
+ if (pcfg.emu_srst_en_prio)
+ log_debug(" - emulate sync reset / enable priority\n");
+ for (auto i: pcfg.emu_trans)
+ log_debug(" - emulate transparency with write port %d\n", i);
+ }
+}
+
+// Go through memory attributes to determine user-requested mapping style.
+void MemMapping::determine_style() {
+ kind = RamKind::Auto;
+ style = "";
+ if (mem.get_bool_attribute(ID::lram)) {
+ kind = RamKind::Huge;
+ return;
+ }
+ for (auto attr: {ID::ram_block, ID::rom_block, ID::ram_style, ID::rom_style, ID::ramstyle, ID::romstyle, ID::syn_ramstyle, ID::syn_romstyle}) {
+ if (mem.has_attribute(attr)) {
+ Const val = mem.attributes.at(attr);
+ if (val == 1) {
+ kind = RamKind::NotLogic;
+ return;
+ }
+ std::string val_s = val.decode_string();
+ for (auto &c: val_s)
+ c = std::tolower(c);
+ // Handled in memory_dff.
+ if (val_s == "no_rw_check")
+ continue;
+ if (val_s == "auto") {
+ // Nothing.
+ } else if (val_s == "logic" || val_s == "registers") {
+ kind = RamKind::Logic;
+ } else if (val_s == "distributed") {
+ kind = RamKind::Distributed;
+ } else if (val_s == "block" || val_s == "block_ram" || val_s == "ebr") {
+ kind = RamKind::Block;
+ } else if (val_s == "huge" || val_s == "ultra") {
+ kind = RamKind::Huge;
+ } else {
+ kind = RamKind::NotLogic;
+ style = val_s;
+ }
+ return;
+ }
+ }
+ if (mem.get_bool_attribute(ID::logic_block))
+ kind = RamKind::Logic;
+}
+
+// Determine whether the memory can be mapped entirely to soft logic.
+bool MemMapping::determine_logic_ok() {
+ if (kind != RamKind::Auto && kind != RamKind::Logic)
+ return false;
+ // Memory is mappable entirely to soft logic iff all its write ports are in the same clock domain.
+ if (mem.wr_ports.empty())
+ return true;
+ for (auto &port: mem.wr_ports) {
+ if (!port.clk_enable)
+ return false;
+ if (port.clk != mem.wr_ports[0].clk)
+ return false;
+ if (port.clk_polarity != mem.wr_ports[0].clk_polarity)
+ return false;
+ }
+ return true;
+}
+
+// Apply RAM kind restrictions (logic/distributed/block/huge), if any.
+bool MemMapping::check_ram_kind(const Ram &ram) {
+ if (style != "")
+ return true;
+ if (ram.kind == kind)
+ return true;
+ if (kind == RamKind::Auto || kind == RamKind::NotLogic) {
+ if (ram.kind == RamKind::Distributed && opts.no_auto_distributed)
+ return false;
+ if (ram.kind == RamKind::Block && opts.no_auto_block)
+ return false;
+ if (ram.kind == RamKind::Huge && opts.no_auto_huge)
+ return false;
+ return true;
+ }
+ return false;
+}
+
+// Apply specific RAM style restrictions, if any.
+bool MemMapping::check_ram_style(const Ram &ram) {
+ if (style == "")
+ return true;
+ for (auto &s: ram.style)
+ if (s == style)
+ return true;
+ return false;
+}
+
+// Handle memory initializer restrictions, if any.
+bool MemMapping::check_init(const Ram &ram) {
+ bool has_nonx = false;
+ bool has_one = false;
+
+ for (auto &init: mem.inits) {
+ if (init.data.is_fully_undef())
+ continue;
+ has_nonx = true;
+ for (auto bit: init.data)
+ if (bit == State::S1)
+ has_one = true;
+ }
+
+ switch (ram.init) {
+ case MemoryInitKind::None:
+ return !has_nonx;
+ case MemoryInitKind::Zero:
+ return !has_one;
+ default:
+ return true;
+ }
+}
+
+bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_polarity) {
+ if (def.clk_shared == -1)
+ return true;
+ auto &cdef = cfg.def->shared_clocks[def.clk_shared];
+ auto &ccfg = cfg.shared_clocks[def.clk_shared];
+ if (cdef.anyedge) {
+ if (!ccfg.used) {
+ ccfg.used = true;
+ ccfg.clk = clk;
+ ccfg.polarity = clk_polarity;
+ return true;
+ } else {
+ return ccfg.clk == clk && ccfg.polarity == clk_polarity;
+ }
+ } else {
+ bool invert = clk_polarity ^ (def.clk_pol == ClkPolKind::Posedge);
+ if (!ccfg.used) {
+ ccfg.used = true;
+ ccfg.clk = clk;
+ ccfg.invert = invert;
+ return true;
+ } else {
+ return ccfg.clk == clk && ccfg.invert == invert;
+ }
+ }
+}
+
+// Perform write port assignment, validating clock options as we go.
+void MemMapping::assign_wr_ports() {
+ for (auto &port: mem.wr_ports) {
+ if (!port.clk_enable) {
+ // Async write ports not supported.
+ cfgs.clear();
+ return;
+ }
+ MemConfigs new_cfgs;
+ for (auto &cfg: cfgs) {
+ for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) {
+ auto &pg = cfg.def->port_groups[pgi];
+ // Make sure the target port group still has a free port.
+ int used = 0;
+ for (auto &oport: cfg.wr_ports)
+ if (oport.port_group == pgi)
+ used++;
+ if (used >= GetSize(pg.names))
+ continue;
+ for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) {
+ auto &def = pg.variants[pvi];
+ // Make sure the target is a write port.
+ if (def.kind == PortKind::Ar || def.kind == PortKind::Sr)
+ continue;
+ MemConfig new_cfg = cfg;
+ WrPortConfig pcfg;
+ pcfg.rd_port = -1;
+ pcfg.port_group = pgi;
+ pcfg.port_variant = pvi;
+ pcfg.def = &def;
+ if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity))
+ continue;
+ new_cfg.wr_ports.push_back(pcfg);
+ new_cfgs.push_back(new_cfg);
+ }
+ }
+ }
+ cfgs = new_cfgs;
+ }
+}
+
+// Perform read port assignment, validating clock and rden options as we go.
+void MemMapping::assign_rd_ports() {
+ for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
+ auto &port = mem.rd_ports[pidx];
+ MemConfigs new_cfgs;
+ for (auto &cfg: cfgs) {
+ // First pass: read port not shared with a write port.
+ for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) {
+ auto &pg = cfg.def->port_groups[pgi];
+ // Make sure the target port group has a port not used up by write ports.
+ // Overuse by other read ports is not a problem — this will just result
+ // in memory duplication.
+ int used = 0;
+ for (auto &oport: cfg.wr_ports)
+ if (oport.port_group == pgi)
+ used++;
+ if (used >= GetSize(pg.names))
+ continue;
+ for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) {
+ auto &def = pg.variants[pvi];
+ // Make sure the target is a read port.
+ if (def.kind == PortKind::Sw)
+ continue;
+ // If mapping an async port, accept only async defs.
+ if (!port.clk_enable) {
+ if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw)
+ continue;
+ }
+ MemConfig new_cfg = cfg;
+ RdPortConfig pcfg;
+ pcfg.wr_port = -1;
+ pcfg.port_group = pgi;
+ pcfg.port_variant = pvi;
+ pcfg.def = &def;
+ if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw) {
+ pcfg.emu_sync = false;
+ if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity))
+ continue;
+ // Decide if rden is usable.
+ if (port.en != State::S1) {
+ if (def.clk_en) {
+ pcfg.rd_en_to_clk_en = true;
+ } else {
+ pcfg.emu_en = !def.rd_en;
+ }
+ }
+ } else {
+ pcfg.emu_sync = port.clk_enable;
+ }
+ new_cfg.rd_ports.push_back(pcfg);
+ new_cfgs.push_back(new_cfg);
+ }
+ }
+ // Second pass: read port shared with a write port.
+ for (int wpidx = 0; wpidx < GetSize(mem.wr_ports); wpidx++) {
+ auto &wport = mem.wr_ports[wpidx];
+ auto &wpcfg = cfg.wr_ports[wpidx];
+ auto &def = *wpcfg.def;
+ // Make sure the write port is not yet shared.
+ if (wpcfg.rd_port != -1)
+ continue;
+ // Make sure the target is a read port.
+ if (def.kind == PortKind::Sw)
+ continue;
+ // Validate address compatibility.
+ if (!addr_compatible(wpidx, pidx))
+ continue;
+ // Validate clock compatibility, if needed.
+ if (def.kind == PortKind::Srsw) {
+ if (!port.clk_enable)
+ continue;
+ if (port.clk != wport.clk)
+ continue;
+ if (port.clk_polarity != wport.clk_polarity)
+ continue;
+ }
+ // Okay, let's fill it in.
+ MemConfig new_cfg = cfg;
+ new_cfg.wr_ports[wpidx].rd_port = pidx;
+ RdPortConfig pcfg;
+ pcfg.wr_port = wpidx;
+ pcfg.port_group = wpcfg.port_group;
+ pcfg.port_variant = wpcfg.port_variant;
+ pcfg.def = wpcfg.def;
+ pcfg.emu_sync = port.clk_enable && def.kind == PortKind::Arsw;
+ // For srsw, check rden capability.
+ if (def.kind == PortKind::Srsw) {
+ bool trans = port.transparency_mask[wpidx];
+ bool col_x = port.collision_x_mask[wpidx];
+ if (def.rdwr == RdWrKind::NoChange) {
+ if (!get_wr_excludes_rd(wpidx, pidx)) {
+ if (!trans && !col_x)
+ continue;
+ if (trans)
+ pcfg.emu_trans.push_back(wpidx);
+ new_cfg.wr_ports[wpidx].force_uniform = true;
+ }
+ if (port.en != State::S1) {
+ if (def.clk_en) {
+ pcfg.rd_en_to_clk_en = true;
+ } else {
+ pcfg.emu_en = !def.rd_en;
+ }
+ }
+ } else {
+ if (!col_x && !trans && def.rdwr != RdWrKind::Old)
+ continue;
+ if (trans) {
+ if (def.rdwr != RdWrKind::New && def.rdwr != RdWrKind::NewOnly)
+ pcfg.emu_trans.push_back(wpidx);
+ }
+ if (def.rdwr == RdWrKind::NewOnly) {
+ if (!get_wr_excludes_rd(wpidx, pidx))
+ new_cfg.wr_ports[wpidx].force_uniform = true;
+ }
+ if (port.en != State::S1) {
+ if (def.clk_en) {
+ if (get_wr_implies_rd(wpidx, pidx)) {
+ pcfg.rd_en_to_clk_en = true;
+ } else {
+ pcfg.emu_en = !def.rd_en;
+ }
+ } else {
+ pcfg.emu_en = !def.rd_en;
+ }
+ }
+ }
+ }
+ new_cfg.rd_ports.push_back(pcfg);
+ new_cfgs.push_back(new_cfg);
+ }
+ }
+ cfgs = new_cfgs;
+ }
+}
+
+// Validate transparency restrictions, determine where to add soft transparency logic.
+void MemMapping::handle_trans() {
+ if (mem.emulate_read_first_ok()) {
+ MemConfigs new_cfgs;
+ for (auto &cfg: cfgs) {
+ new_cfgs.push_back(cfg);
+ bool ok = true;
+ // Using this trick will break read-write port sharing.
+ for (auto &pcfg: cfg.rd_ports)
+ if (pcfg.wr_port != -1)
+ ok = false;
+ if (ok) {
+ cfg.emu_read_first = true;
+ new_cfgs.push_back(cfg);
+ }
+ }
+ cfgs = new_cfgs;
+ }
+ for (int rpidx = 0; rpidx < GetSize(mem.rd_ports); rpidx++) {
+ auto &rport = mem.rd_ports[rpidx];
+ if (!rport.clk_enable)
+ continue;
+ for (int wpidx = 0; wpidx < GetSize(mem.wr_ports); wpidx++) {
+ auto &wport = mem.wr_ports[wpidx];
+ if (!wport.clk_enable)
+ continue;
+ if (rport.clk != wport.clk)
+ continue;
+ if (rport.clk_polarity != wport.clk_polarity)
+ continue;
+ // If we got this far, we have a transparency restriction
+ // to uphold.
+ MemConfigs new_cfgs;
+ for (auto &cfg: cfgs) {
+ auto &rpcfg = cfg.rd_ports[rpidx];
+ auto &wpcfg = cfg.wr_ports[wpidx];
+ // The transparency relation for shared ports already handled while assigning them.
+ if (rpcfg.wr_port == wpidx) {
+ new_cfgs.push_back(cfg);
+ continue;
+ }
+ if (rport.collision_x_mask[wpidx] && !cfg.emu_read_first) {
+ new_cfgs.push_back(cfg);
+ continue;
+ }
+ bool transparent = rport.transparency_mask[wpidx] || cfg.emu_read_first;
+ if (rpcfg.emu_sync) {
+ // For async read port, just add the transparency logic
+ // if necessary.
+ if (transparent)
+ rpcfg.emu_trans.push_back(wpidx);
+ new_cfgs.push_back(cfg);
+ } else {
+ // Otherwise, split through the relevant wrtrans caps.
+ // For non-transparent ports, the cap needs to be present.
+ // For transparent ports, we can emulate transparency
+ // even without a direct cap.
+ bool found = false;
+ for (auto &tdef: wpcfg.def->wrtrans) {
+ // Check if the target matches.
+ if (tdef.target_kind == WrTransTargetKind::Group && rpcfg.port_group != tdef.target_group)
+ continue;
+ // Check if the transparency kind is acceptable.
+ if (transparent) {
+ if (tdef.kind == WrTransKind::Old)
+ continue;
+ } else {
+ if (tdef.kind != WrTransKind::Old)
+ continue;
+ }
+ // Okay, we can use this cap.
+ new_cfgs.push_back(cfg);
+ found = true;
+ break;
+ }
+ if (!found && transparent) {
+ // If the port pair is transparent, but no cap was
+ // found, use emulation.
+ rpcfg.emu_trans.push_back(wpidx);
+ new_cfgs.push_back(cfg);
+ }
+ }
+ }
+ cfgs = new_cfgs;
+ }
+ }
+}
+
+// Determine where to add soft priority logic.
+void MemMapping::handle_priority() {
+ for (int p1idx = 0; p1idx < GetSize(mem.wr_ports); p1idx++) {
+ for (int p2idx = 0; p2idx < GetSize(mem.wr_ports); p2idx++) {
+ auto &port2 = mem.wr_ports[p2idx];
+ if (!port2.priority_mask[p1idx])
+ continue;
+ for (auto &cfg: cfgs) {
+ auto &p1cfg = cfg.rd_ports[p1idx];
+ auto &p2cfg = cfg.wr_ports[p2idx];
+ bool found = false;
+ for (auto &pgi: p2cfg.def->wrprio) {
+ if (pgi == p1cfg.port_group) {
+ found = true;
+ break;
+ }
+ }
+ // If no cap was found, emulate.
+ if (!found)
+ p2cfg.emu_prio.push_back(p1idx);
+ }
+ }
+ }
+}
+
+bool is_all_zero(const Const &val) {
+ for (auto bit: val.bits)
+ if (bit == State::S1)
+ return false;
+ return true;
+}
+
+// Determine where to add soft init value / reset logic.
+void MemMapping::handle_rd_rst() {
+ for (auto &cfg: cfgs) {
+ for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
+ auto &port = mem.rd_ports[pidx];
+ auto &pcfg = cfg.rd_ports[pidx];
+ // Only sync ports are relevant.
+ // If emulated by async port or we already emulate CE, init will be
+ // included for free.
+ if (!port.clk_enable || pcfg.emu_sync || pcfg.emu_en)
+ continue;
+ switch (pcfg.def->rdinitval) {
+ case ResetValKind::None:
+ pcfg.emu_init = !port.init_value.is_fully_undef();
+ break;
+ case ResetValKind::Zero:
+ pcfg.emu_init = !is_all_zero(port.init_value);
+ break;
+ default:
+ break;
+ }
+ Const init_val = port.init_value;
+ if (port.arst != State::S0) {
+ switch (pcfg.def->rdarstval) {
+ case ResetValKind::None:
+ pcfg.emu_arst = true;
+ break;
+ case ResetValKind::Zero:
+ pcfg.emu_arst = !is_all_zero(port.arst_value);
+ break;
+ case ResetValKind::Init:
+ if (init_val.is_fully_undef())
+ init_val = port.arst_value;
+ pcfg.emu_arst = init_val != port.arst_value;
+ break;
+ default:
+ break;
+ }
+ }
+ if (port.srst != State::S0) {
+ switch (pcfg.def->rdsrstval) {
+ case ResetValKind::None:
+ pcfg.emu_srst = true;
+ break;
+ case ResetValKind::Zero:
+ pcfg.emu_srst = !is_all_zero(port.srst_value);
+ break;
+ case ResetValKind::Init:
+ if (init_val.is_fully_undef())
+ init_val = port.srst_value;
+ pcfg.emu_srst = init_val != port.srst_value;
+ break;
+ default:
+ break;
+ }
+ if (!pcfg.emu_srst && pcfg.def->rdsrst_block_wr && pcfg.wr_port != -1) {
+ if (!get_wr_excludes_srst(pcfg.wr_port, pidx))
+ pcfg.emu_srst = true;
+ }
+ if (!pcfg.emu_srst && port.en != State::S1) {
+ if (port.ce_over_srst) {
+ switch (pcfg.def->rdsrstmode) {
+ case SrstKind::Ungated:
+ pcfg.emu_srst_en_prio = true;
+ break;
+ case SrstKind::GatedClkEn:
+ pcfg.emu_srst_en_prio = !pcfg.rd_en_to_clk_en;
+ break;
+ case SrstKind::GatedRdEn:
+ break;
+ default:
+ log_assert(0);
+ }
+ } else {
+ switch (pcfg.def->rdsrstmode) {
+ case SrstKind::Ungated:
+ break;
+ case SrstKind::GatedClkEn:
+ if (pcfg.rd_en_to_clk_en) {
+ if (pcfg.def->rd_en) {
+ pcfg.rd_en_to_clk_en = false;
+ } else {
+ pcfg.emu_srst_en_prio = true;
+ }
+ }
+ break;
+ case SrstKind::GatedRdEn:
+ pcfg.emu_srst_en_prio = true;
+ break;
+ default:
+ log_assert(0);
+ }
+ }
+ }
+ } else {
+ if (pcfg.def->rd_en && pcfg.def->rdwr == RdWrKind::NoChange && pcfg.wr_port != -1) {
+ pcfg.rd_en_to_clk_en = false;
+ }
+ }
+ }
+ }
+}
+
+void MemMapping::score_emu_ports() {
+ for (auto &cfg: cfgs) {
+ std::vector<int> port_usage_wr(cfg.def->port_groups.size());
+ std::vector<int> port_usage_rd(cfg.def->port_groups.size());
+ int score = 0;
+ // 3 points for every write port if we need to do read-first emulation.
+ if (cfg.emu_read_first)
+ score += 3 * GetSize(cfg.wr_ports);
+ for (auto &pcfg: cfg.wr_ports) {
+ // 1 point for every priority relation we need to fix up.
+ // This is just a gate for every distinct wren pair.
+ score += GetSize(pcfg.emu_prio);
+ port_usage_wr[pcfg.port_group]++;
+ }
+ for (auto &pcfg: cfg.rd_ports) {
+ // 3 points for every soft transparency logic instance. This involves
+ // registers and other major mess.
+ score += 3 * GetSize(pcfg.emu_trans);
+ // 3 points for CE soft logic. Likewise involves registers.
+ // If we already do this, subsumes any init/srst/arst emulation.
+ if (pcfg.emu_en)
+ score += 3;
+ // 2 points for soft init value / reset logic: involves single bit
+ // register and some muxes.
+ if (pcfg.emu_init)
+ score += 2;
+ if (pcfg.emu_arst)
+ score += 2;
+ if (pcfg.emu_srst)
+ score += 2;
+ // 1 point for wrong srst/en priority (fixed with a single gate).
+ if (pcfg.emu_srst_en_prio)
+ score++;
+ // 1 point for every non-shared read port used, as a tiebreaker
+ // to prefer single-port configs.
+ if (pcfg.wr_port == -1) {
+ score++;
+ port_usage_rd[pcfg.port_group]++;
+ }
+ }
+ cfg.score_emu = score;
+ int repl_port = 1;
+ for (int i = 0; i < GetSize(cfg.def->port_groups); i++) {
+ int space = GetSize(cfg.def->port_groups[i].names) - port_usage_wr[i];
+ log_assert(space >= 0);
+ if (port_usage_rd[i] > 0) {
+ log_assert(space > 0);
+ int usage = port_usage_rd[i];
+ int cur = (usage + space - 1) / space;
+ if (cur > repl_port)
+ repl_port = cur;
+ }
+ }
+ cfg.repl_port = repl_port;
+ }
+}
+
+void MemMapping::handle_geom() {
+ std::vector<int> wren_size;
+ for (auto &port: mem.wr_ports) {
+ SigSpec en = port.en;
+ en.sort_and_unify();
+ wren_size.push_back(GetSize(en));
+ }
+ for (auto &cfg: cfgs) {
+ // First, create a set of "byte boundaries": the bit positions in source memory word
+ // that have write enable different from the previous bit in any write port.
+ // Bit 0 is considered to be a byte boundary as well.
+ // Likewise, create a set of "word boundaries" that are like above, but only for write ports
+ // with the "force uniform" flag set.
+ std::vector<bool> byte_boundary(mem.width, false);
+ std::vector<bool> word_boundary(mem.width, false);
+ byte_boundary[0] = true;
+ for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) {
+ auto &port = mem.wr_ports[pidx];
+ auto &pcfg = cfg.wr_ports[pidx];
+ if (pcfg.force_uniform)
+ word_boundary[0] = true;
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++) {
+ for (int i = 1; i < mem.width; i++) {
+ int pos = sub * mem.width + i;
+ if (port.en[pos] != port.en[pos-1]) {
+ byte_boundary[i] = true;
+ if (pcfg.force_uniform)
+ word_boundary[i] = true;
+ }
+ }
+ }
+ }
+ bool got_config = false;
+ int best_cost = 0;
+ int byte_width_log2 = 0;
+ for (int i = 0; i < GetSize(cfg.def->dbits); i++)
+ if (cfg.def->byte >= cfg.def->dbits[i])
+ byte_width_log2 = i;
+ if (cfg.def->byte == 0)
+ byte_width_log2 = GetSize(cfg.def->dbits) - 1;
+ pool<int> no_wide_bits;
+ // Determine which of the source address bits involved in wide ports
+ // are "uniform". Bits are considered uniform if, when a port is widened through
+ // them, the write enables are the same for both values of the bit.
+ int max_wr_wide_log2 = 0;
+ for (auto &port: mem.wr_ports)
+ if (port.wide_log2 > max_wr_wide_log2)
+ max_wr_wide_log2 = port.wide_log2;
+ int max_wide_log2 = max_wr_wide_log2;
+ for (auto &port: mem.rd_ports)
+ if (port.wide_log2 > max_wide_log2)
+ max_wide_log2 = port.wide_log2;
+ int wide_nu_start = max_wide_log2;
+ int wide_nu_end = max_wr_wide_log2;
+ for (int i = 0; i < GetSize(mem.wr_ports); i++) {
+ auto &port = mem.wr_ports[i];
+ auto &pcfg = cfg.wr_ports[i];
+ for (int j = 0; j < port.wide_log2; j++) {
+ bool uniform = true;
+ // If write enables don't match, mark bit as non-uniform.
+ for (int k = 0; k < (1 << port.wide_log2); k += 2 << j)
+ if (port.en.extract(k * mem.width, mem.width << j) != port.en.extract((k + (1 << j)) * mem.width, mem.width << j))
+ uniform = false;
+ if (!uniform) {
+ if (pcfg.force_uniform) {
+ for (int k = j; k < port.wide_log2; k++)
+ no_wide_bits.insert(k);
+ }
+ if (j < wide_nu_start)
+ wide_nu_start = j;
+ break;
+ }
+ }
+ if (pcfg.def->width_tied && pcfg.rd_port != -1) {
+ // If:
+ //
+ // - the write port is merged with a read port
+ // - the read port is wider than the write port
+ // - read and write widths are tied
+ //
+ // then we will have to artificially widen the write
+ // port to the width of the read port, and emulate
+ // a narrower write path by use of write enables,
+ // which will definitely be non-uniform over the added
+ // bits.
+ auto &rport = mem.rd_ports[pcfg.rd_port];
+ if (rport.wide_log2 > port.wide_log2) {
+ if (port.wide_log2 < wide_nu_start)
+ wide_nu_start = port.wide_log2;
+ if (rport.wide_log2 > wide_nu_end)
+ wide_nu_end = rport.wide_log2;
+ if (pcfg.force_uniform) {
+ for (int k = port.wide_log2; k < rport.wide_log2; k++)
+ no_wide_bits.insert(k);
+ }
+ }
+ }
+ }
+ // Iterate over base widths.
+ for (int base_width_log2 = 0; base_width_log2 < GetSize(cfg.def->dbits); base_width_log2++) {
+ // Now, see how many data bits we actually have available.
+ // This is usually dbits[base_width_log2], but could be smaller if we
+ // ran afoul of a max width limitation. Configurations where this
+ // happens are not useful, unless we need it to satisfy a *minimum*
+ // width limitation.
+ int unit_width_log2 = base_width_log2;
+ for (auto &pcfg: cfg.wr_ports)
+ if (unit_width_log2 > pcfg.def->max_wr_wide_log2)
+ unit_width_log2 = pcfg.def->max_wr_wide_log2;
+ for (auto &pcfg: cfg.rd_ports)
+ if (unit_width_log2 > pcfg.def->max_rd_wide_log2)
+ unit_width_log2 = pcfg.def->max_rd_wide_log2;
+ if (unit_width_log2 != base_width_log2 && got_config)
+ break;
+ int unit_width = cfg.def->dbits[unit_width_log2];
+ // Also determine effective byte width (the granularity of write enables).
+ int effective_byte = cfg.def->byte;
+ if (effective_byte == 0 || effective_byte > unit_width)
+ effective_byte = unit_width;
+ if (mem.wr_ports.empty())
+ effective_byte = 1;
+ log_assert(unit_width % effective_byte == 0);
+ // Create the swizzle pattern.
+ std::vector<int> swizzle;
+ for (int i = 0; i < mem.width; i++) {
+ if (word_boundary[i])
+ while (GetSize(swizzle) % unit_width)
+ swizzle.push_back(-1);
+ else if (byte_boundary[i])
+ while (GetSize(swizzle) % effective_byte)
+ swizzle.push_back(-1);
+ swizzle.push_back(i);
+ }
+ if (word_boundary[0])
+ while (GetSize(swizzle) % unit_width)
+ swizzle.push_back(-1);
+ else
+ while (GetSize(swizzle) % effective_byte)
+ swizzle.push_back(-1);
+ // Now evaluate the configuration, then keep adding more hard wide bits
+ // and evaluating.
+ int hard_wide_mask = 0;
+ int hard_wide_num = 0;
+ bool byte_failed = false;
+ while (1) {
+ // Check if all min width constraints are satisfied.
+ // Only check these constraints for write ports with width below
+ // byte width — for other ports, we can emulate narrow width with
+ // a larger one.
+ bool min_width_ok = true;
+ int min_width_bit = wide_nu_start;
+ for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) {
+ auto &port = mem.wr_ports[pidx];
+ int w = base_width_log2;
+ for (int i = 0; i < port.wide_log2; i++)
+ if (hard_wide_mask & 1 << i)
+ w++;
+ if (w < cfg.wr_ports[pidx].def->min_wr_wide_log2 && w < byte_width_log2) {
+ min_width_ok = false;
+ if (min_width_bit > port.wide_log2)
+ min_width_bit = port.wide_log2;
+ }
+ }
+ if (min_width_ok) {
+ int emu_wide_bits = max_wide_log2 - hard_wide_num;
+ int mult_wide = 1 << emu_wide_bits;
+ int addrs = 1 << (cfg.def->abits - base_width_log2 + emu_wide_bits);
+ int min_addr = mem.start_offset / addrs;
+ int max_addr = (mem.start_offset + mem.size - 1) / addrs;
+ int mult_a = max_addr - min_addr + 1;
+ int bits = mult_a * mult_wide * GetSize(swizzle);
+ int repl = (bits + unit_width - 1) / unit_width;
+ int score_demux = 0;
+ for (int i = 0; i < GetSize(mem.wr_ports); i++) {
+ auto &port = mem.wr_ports[i];
+ int w = emu_wide_bits;
+ for (int i = 0; i < port.wide_log2; i++)
+ if (!(hard_wide_mask & 1 << i))
+ w--;
+ if (w || mult_a != 1)
+ score_demux += (mult_a << w) * wren_size[i];
+ }
+ int score_mux = 0;
+ for (auto &port: mem.rd_ports) {
+ int w = emu_wide_bits;
+ for (int i = 0; i < port.wide_log2; i++)
+ if (!(hard_wide_mask & 1 << i))
+ w--;
+ score_mux += ((mult_a << w) - 1) * GetSize(port.data);
+ }
+ double cost = (cfg.def->cost - cfg.def->widthscale) * repl * cfg.repl_port;
+ cost += cfg.def->widthscale * mult_a * mult_wide * mem.width / unit_width * cfg.repl_port;
+ cost += score_mux * FACTOR_MUX;
+ cost += score_demux * FACTOR_DEMUX;
+ cost += cfg.score_emu * FACTOR_EMU;
+ if (!got_config || cost < best_cost) {
+ cfg.base_width_log2 = base_width_log2;
+ cfg.unit_width_log2 = unit_width_log2;
+ cfg.swizzle = swizzle;
+ cfg.hard_wide_mask = hard_wide_mask;
+ cfg.emu_wide_mask = ((1 << max_wide_log2) - 1) & ~hard_wide_mask;
+ cfg.repl_d = repl;
+ cfg.score_demux = score_demux;
+ cfg.score_mux = score_mux;
+ cfg.cost = cost;
+ best_cost = cost;
+ got_config = true;
+ }
+ }
+ if (cfg.def->width_mode != WidthMode::PerPort)
+ break;
+ // Now, pick the next bit to add to the hard wide mask.
+next_hw:
+ int scan_from;
+ int scan_to;
+ bool retry = false;
+ if (!min_width_ok) {
+ // If we still haven't met the minimum width limits,
+ // add the highest one that will be useful for working
+ // towards all unmet limits.
+ scan_from = min_width_bit;
+ scan_to = 0;
+ // If the relevant write port is not wide, it's impossible.
+ } else if (byte_failed) {
+ // If we already failed with uniformly-written bits only,
+ // go with uniform bits that are only involved in reads.
+ scan_from = max_wide_log2;
+ scan_to = wide_nu_end;
+ } else if (base_width_log2 + hard_wide_num < byte_width_log2) {
+ // If we still need uniform bits, prefer the low ones.
+ scan_from = wide_nu_start;
+ scan_to = 0;
+ retry = true;
+ } else {
+ scan_from = max_wide_log2;
+ scan_to = 0;
+ }
+ int bit = scan_from - 1;
+ while (1) {
+ if (bit < scan_to) {
+hw_bit_failed:
+ if (retry) {
+ byte_failed = true;
+ goto next_hw;
+ } else {
+ goto bw_done;
+ }
+ }
+ if (!(hard_wide_mask & 1 << bit) && !no_wide_bits.count(bit))
+ break;
+ bit--;
+ }
+ int new_hw_mask = hard_wide_mask | 1 << bit;
+ // Check if all max width constraints are satisfied.
+ for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) {
+ auto &port = mem.wr_ports[pidx];
+ int w = base_width_log2;
+ for (int i = 0; i < port.wide_log2; i++)
+ if (new_hw_mask & 1 << i)
+ w++;
+ if (w > cfg.wr_ports[pidx].def->max_wr_wide_log2) {
+ goto hw_bit_failed;
+ }
+ }
+ for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
+ auto &port = mem.rd_ports[pidx];
+ int w = base_width_log2;
+ for (int i = 0; i < port.wide_log2; i++)
+ if (new_hw_mask & 1 << i)
+ w++;
+ if (w > cfg.rd_ports[pidx].def->max_rd_wide_log2) {
+ goto hw_bit_failed;
+ }
+ }
+ // Bit ok, commit.
+ hard_wide_mask = new_hw_mask;
+ hard_wide_num++;
+ }
+bw_done:;
+ }
+ log_assert(got_config);
+ }
+}
+
+void MemMapping::prune_post_geom() {
+ std::vector<bool> keep;
+ dict<std::string, int> rsrc;
+ for (int i = 0; i < GetSize(cfgs); i++) {
+ auto &cfg = cfgs[i];
+ std::string key = cfg.def->resource_name;
+ if (key.empty()) {
+ switch (cfg.def->kind) {
+ case RamKind::Distributed:
+ key = "[distributed]";
+ break;
+ case RamKind::Block:
+ key = "[block]";
+ break;
+ case RamKind::Huge:
+ key = "[huge]";
+ break;
+ default:
+ break;
+ }
+ }
+ auto it = rsrc.find(key);
+ if (it == rsrc.end()) {
+ rsrc[key] = i;
+ keep.push_back(true);
+ } else {
+ auto &ocfg = cfgs[it->second];
+ if (cfg.cost < ocfg.cost) {
+ keep[it->second] = false;
+ it->second = i;
+ keep.push_back(true);
+ } else {
+ keep.push_back(false);
+ }
+ }
+ }
+ MemConfigs new_cfgs;
+ for (int i = 0; i < GetSize(cfgs); i++)
+ if (keep[i])
+ new_cfgs.push_back(cfgs[i]);
+ cfgs = new_cfgs;
+}
+
+Swizzle gen_swizzle(const Mem &mem, const MemConfig &cfg, int sw_wide_log2, int hw_wide_log2) {
+ Swizzle res;
+
+ std::vector<int> emu_wide_bits;
+ std::vector<int> hard_wide_bits;
+ for (int i = 0; i < ceil_log2(mem.size); i++) {
+ if (cfg.emu_wide_mask & 1 << i)
+ emu_wide_bits.push_back(i);
+ else if (GetSize(hard_wide_bits) < hw_wide_log2 - cfg.base_width_log2)
+ hard_wide_bits.push_back(i);
+ }
+ for (int x : hard_wide_bits)
+ if (x >= sw_wide_log2)
+ res.addr_mux_bits.push_back(x);
+ for (int x : emu_wide_bits)
+ if (x >= sw_wide_log2)
+ res.addr_mux_bits.push_back(x);
+
+ res.addr_shift = cfg.def->abits - cfg.base_width_log2 + GetSize(emu_wide_bits);
+ res.addr_start = mem.start_offset & ~((1 << res.addr_shift) - 1);
+ res.addr_end = ((mem.start_offset + mem.size - 1) | ((1 << res.addr_shift) - 1)) + 1;
+ int hnum = (res.addr_end - res.addr_start) >> res.addr_shift;
+ int unit_width = cfg.def->dbits[cfg.unit_width_log2];
+
+ for (int rd = 0; rd < cfg.repl_d; rd++) {
+ std::vector<SwizzleBit> bits(cfg.def->dbits[hw_wide_log2]);
+ for (auto &bit: bits)
+ bit.valid = false;
+ res.bits.push_back(bits);
+ }
+
+ for (int hi = 0; hi < hnum; hi++) {
+ for (int ewi = 0; ewi < (1 << GetSize(emu_wide_bits)); ewi++) {
+ for (int hwi = 0; hwi < (1 << GetSize(hard_wide_bits)); hwi++) {
+ int mux_idx = 0;
+ int sub = 0;
+ int mib = 0;
+ int hbit_base = 0;
+ for (int i = 0; i < GetSize(hard_wide_bits); i++) {
+ if (hard_wide_bits[i] < sw_wide_log2) {
+ if (hwi & 1 << i)
+ sub |= 1 << hard_wide_bits[i];
+ } else {
+ if (hwi & 1 << i)
+ mux_idx |= 1 << mib;
+ mib++;
+ }
+ if (hwi & 1 << i)
+ hbit_base += cfg.def->dbits[i + cfg.base_width_log2];
+ }
+ for (int i = 0; i < GetSize(emu_wide_bits); i++) {
+ if (emu_wide_bits[i] < sw_wide_log2) {
+ if (ewi & 1 << i)
+ sub |= 1 << emu_wide_bits[i];
+ } else {
+ if (ewi & 1 << i)
+ mux_idx |= 1 << mib;
+ mib++;
+ }
+ }
+ mux_idx |= hi << mib;
+ int addr = res.addr_start + (hi << res.addr_shift);
+ for (int i = 0; i < GetSize(res.addr_mux_bits); i++)
+ if (mux_idx & 1 << i)
+ addr += 1 << res.addr_mux_bits[i];
+ for (int bit = 0; bit < GetSize(cfg.swizzle); bit++) {
+ if (cfg.swizzle[bit] == -1)
+ continue;
+ int rbit = bit + GetSize(cfg.swizzle) * (ewi + (hi << GetSize(emu_wide_bits)));
+ int rep = rbit / unit_width;
+ int hbit = hbit_base + rbit % unit_width;
+ auto &swz = res.bits[rep][hbit];
+ swz.valid = true;
+ swz.addr = addr;
+ swz.mux_idx = mux_idx;
+ swz.bit = cfg.swizzle[bit] + sub * mem.width;
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+void clean_undef(std::vector<State> &val) {
+ for (auto &bit : val)
+ if (bit != State::S1)
+ bit = State::S0;
+}
+
+std::vector<SigSpec> generate_demux(Mem &mem, int wpidx, const Swizzle &swz) {
+ auto &port = mem.wr_ports[wpidx];
+ std::vector<SigSpec> res;
+ int hi_bits = ceil_log2(swz.addr_end - swz.addr_start) - swz.addr_shift;
+ auto compressed = port.compress_en();
+ SigSpec sig_a = compressed.first;
+ SigSpec addr = port.addr;
+ if (GetSize(addr) > hi_bits + swz.addr_shift) {
+ int lo = mem.start_offset;
+ int hi = mem.start_offset + mem.size;
+ int bits = ceil_log2(hi);
+ for (int i = 0; i < bits; i++) {
+ int new_lo = lo;
+ if (lo & 1 << i)
+ new_lo -= 1 << i;
+ int new_hi = hi;
+ if (hi & 1 << i)
+ new_hi += 1 << i;
+ if (new_hi - new_lo > (1 << (hi_bits + swz.addr_shift)))
+ break;
+ lo = new_lo;
+ hi = new_hi;
+ }
+ SigSpec in_range = mem.module->And(NEW_ID, mem.module->Ge(NEW_ID, addr, lo), mem.module->Lt(NEW_ID, addr, hi));
+ sig_a = mem.module->Mux(NEW_ID, Const(State::S0, GetSize(sig_a)), sig_a, in_range);
+ }
+ addr.extend_u0(swz.addr_shift + hi_bits, false);
+ SigSpec sig_s;
+ for (int x : swz.addr_mux_bits)
+ sig_s.append(addr[x]);
+ for (int i = 0; i < hi_bits; i++)
+ sig_s.append(addr[swz.addr_shift + i]);
+ SigSpec sig_y;
+ if (GetSize(sig_s) == 0)
+ sig_y = sig_a;
+ else
+ sig_y = mem.module->Demux(NEW_ID, sig_a, sig_s);
+ for (int i = 0; i < ((swz.addr_end - swz.addr_start) >> swz.addr_shift); i++) {
+ for (int j = 0; j < (1 << GetSize(swz.addr_mux_bits)); j++) {
+ int hi = ((swz.addr_start >> swz.addr_shift) + i) & ((1 << hi_bits) - 1);
+ int pos = (hi << GetSize(swz.addr_mux_bits) | j) * GetSize(sig_a);
+ res.push_back(port.decompress_en(compressed.second, sig_y.extract(pos, GetSize(sig_a))));
+ }
+ }
+ return res;
+}
+
+std::vector<SigSpec> generate_mux(Mem &mem, int rpidx, const Swizzle &swz) {
+ auto &port = mem.rd_ports[rpidx];
+ std::vector<SigSpec> res;
+ int hi_bits = ceil_log2(swz.addr_end - swz.addr_start) - swz.addr_shift;
+ SigSpec sig_s;
+ SigSpec addr = port.addr;
+ addr.extend_u0(swz.addr_shift + hi_bits, false);
+ for (int x : swz.addr_mux_bits)
+ sig_s.append(addr[x]);
+ for (int i = 0; i < hi_bits; i++)
+ sig_s.append(addr[swz.addr_shift + i]);
+ if (GetSize(sig_s) == 0) {
+ return {port.data};
+ }
+ if (port.clk_enable) {
+ SigSpec new_sig_s = mem.module->addWire(NEW_ID, GetSize(sig_s));
+ mem.module->addDffe(NEW_ID, port.clk, port.en, sig_s, new_sig_s, port.clk_polarity);
+ sig_s = new_sig_s;
+ }
+ SigSpec sig_a = Const(State::Sx, GetSize(port.data) << hi_bits << GetSize(swz.addr_mux_bits));
+ for (int i = 0; i < ((swz.addr_end - swz.addr_start) >> swz.addr_shift); i++) {
+ for (int j = 0; j < (1 << GetSize(swz.addr_mux_bits)); j++) {
+ SigSpec sig = mem.module->addWire(NEW_ID, GetSize(port.data));
+ int hi = ((swz.addr_start >> swz.addr_shift) + i) & ((1 << hi_bits) - 1);
+ int pos = (hi << GetSize(swz.addr_mux_bits) | j) * GetSize(port.data);
+ for (int k = 0; k < GetSize(port.data); k++)
+ sig_a[pos + k] = sig[k];
+ res.push_back(sig);
+ }
+ }
+ mem.module->addBmux(NEW_ID, sig_a, sig_s, port.data);
+ return res;
+}
+
+void MemMapping::emit_port(const MemConfig &cfg, std::vector<Cell*> &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector<int> &hw_addr_swizzle) {
+ for (auto &it: pdef.options)
+ for (auto cell: cells)
+ cell->setParam(stringf("\\PORT_%s_OPTION_%s", name, it.first.c_str()), it.second);
+ SigSpec addr = Const(State::Sx, cfg.def->abits);
+ int wide_log2 = 0, wr_wide_log2 = 0, rd_wide_log2 = 0;
+ SigSpec clk = State::S0;
+ SigSpec clk_en = State::S0;
+ bool clk_pol = true;
+ if (wpidx != -1) {
+ auto &wport = mem.wr_ports[wpidx];
+ clk = wport.clk;
+ clk_pol = wport.clk_polarity;
+ addr = wport.addr;
+ wide_log2 = wr_wide_log2 = wport.wide_log2;
+ if (rpidx != -1) {
+ auto &rport = mem.rd_ports[rpidx];
+ auto &rpcfg = cfg.rd_ports[rpidx];
+ rd_wide_log2 = rport.wide_log2;
+ if (rd_wide_log2 > wr_wide_log2)
+ wide_log2 = rd_wide_log2;
+ else
+ addr = rport.addr;
+ if (pdef.clk_en) {
+ if (rpcfg.rd_en_to_clk_en) {
+ if (pdef.rdwr == RdWrKind::NoChange) {
+ clk_en = mem.module->Or(NEW_ID, rport.en, mem.module->ReduceOr(NEW_ID, wport.en));
+ } else {
+ clk_en = rport.en;
+ }
+ } else {
+ clk_en = State::S1;
+ }
+ }
+ } else {
+ if (pdef.clk_en)
+ clk_en = State::S1;
+ }
+ } else if (rpidx != -1) {
+ auto &rport = mem.rd_ports[rpidx];
+ auto &rpcfg = cfg.rd_ports[rpidx];
+ if (rport.clk_enable) {
+ clk = rport.clk;
+ clk_pol = rport.clk_polarity;
+ }
+ addr = rport.addr;
+ wide_log2 = rd_wide_log2 = rport.wide_log2;
+ if (pdef.clk_en) {
+ if (rpcfg.rd_en_to_clk_en)
+ clk_en = rport.en;
+ else
+ clk_en = State::S1;
+ }
+
+ }
+ addr = worker.sigmap_xmux(addr);
+ if (pdef.kind != PortKind::Ar) {
+ switch (pdef.clk_pol) {
+ case ClkPolKind::Posedge:
+ if (!clk_pol)
+ clk = mem.module->Not(NEW_ID, clk);
+ break;
+ case ClkPolKind::Negedge:
+ if (clk_pol)
+ clk = mem.module->Not(NEW_ID, clk);
+ break;
+ case ClkPolKind::Anyedge:
+ for (auto cell: cells)
+ cell->setParam(stringf("\\PORT_%s_CLK_POL", name), clk_pol);
+ }
+ for (auto cell: cells) {
+ cell->setPort(stringf("\\PORT_%s_CLK", name), clk);
+ if (pdef.clk_en)
+ cell->setPort(stringf("\\PORT_%s_CLK_EN", name), clk_en);
+ }
+ }
+
+ // Width determination.
+ if (pdef.width_tied) {
+ rd_wide_log2 = wr_wide_log2 = wide_log2;
+ }
+ int hw_wr_wide_log2 = cfg.base_width_log2;
+ for (int i = 0; i < wr_wide_log2; i++)
+ if (cfg.hard_wide_mask & (1 << i))
+ hw_wr_wide_log2++;
+ if (hw_wr_wide_log2 < pdef.min_wr_wide_log2)
+ hw_wr_wide_log2 = pdef.min_wr_wide_log2;
+ if (hw_wr_wide_log2 > pdef.max_wr_wide_log2)
+ hw_wr_wide_log2 = pdef.max_wr_wide_log2;
+ int hw_rd_wide_log2 = cfg.base_width_log2;
+ for (int i = 0; i < rd_wide_log2; i++)
+ if (cfg.hard_wide_mask & (1 << i))
+ hw_rd_wide_log2++;
+ if (hw_rd_wide_log2 < pdef.min_rd_wide_log2)
+ hw_rd_wide_log2 = pdef.min_rd_wide_log2;
+ if (hw_rd_wide_log2 > pdef.max_rd_wide_log2)
+ hw_rd_wide_log2 = pdef.max_rd_wide_log2;
+ if (pdef.width_tied) {
+ // For unused ports, pick max available width,
+ // in case narrow ports require disabling parity
+ // bits etc.
+ if (wpidx == -1 && rpidx == -1) {
+ hw_wr_wide_log2 = pdef.max_wr_wide_log2;
+ hw_rd_wide_log2 = pdef.max_rd_wide_log2;
+ }
+ } else {
+ if (wpidx == -1)
+ hw_wr_wide_log2 = pdef.max_wr_wide_log2;
+ if (rpidx == -1)
+ hw_rd_wide_log2 = pdef.max_rd_wide_log2;
+ }
+ if (cfg.def->width_mode == WidthMode::PerPort) {
+ for (auto cell: cells) {
+ if (pdef.width_tied) {
+ cell->setParam(stringf("\\PORT_%s_WIDTH", name), cfg.def->dbits[hw_wr_wide_log2]);
+ } else {
+ if (pdef.kind != PortKind::Ar && pdef.kind != PortKind::Sr)
+ cell->setParam(stringf("\\PORT_%s_WR_WIDTH", name), cfg.def->dbits[hw_wr_wide_log2]);
+ if (pdef.kind != PortKind::Sw)
+ cell->setParam(stringf("\\PORT_%s_RD_WIDTH", name), cfg.def->dbits[hw_rd_wide_log2]);
+ }
+ }
+ }
+
+ // Address determination.
+ SigSpec hw_addr;
+ for (int x: hw_addr_swizzle) {
+ if (x == -1 || x >= GetSize(addr))
+ hw_addr.append(State::S0);
+ else
+ hw_addr.append(addr[x]);
+ }
+ for (int i = 0; i < hw_wr_wide_log2 && i < hw_rd_wide_log2; i++)
+ hw_addr[i] = State::S0;
+ for (auto cell: cells)
+ cell->setPort(stringf("\\PORT_%s_ADDR", name), hw_addr);
+
+ // Write part.
+ if (pdef.kind != PortKind::Ar && pdef.kind != PortKind::Sr) {
+ int width = cfg.def->dbits[hw_wr_wide_log2];
+ int effective_byte = cfg.def->byte;
+ if (effective_byte == 0 || effective_byte > width)
+ effective_byte = width;
+ if (wpidx != -1) {
+ auto &wport = mem.wr_ports[wpidx];
+ Swizzle port_swz = gen_swizzle(mem, cfg, wport.wide_log2, hw_wr_wide_log2);
+ std::vector<SigSpec> big_wren = generate_demux(mem, wpidx, port_swz);
+ for (int rd = 0; rd < cfg.repl_d; rd++) {
+ auto cell = cells[rd];
+ SigSpec hw_wdata;
+ SigSpec hw_wren;
+ for (auto &bit : port_swz.bits[rd]) {
+ if (!bit.valid) {
+ hw_wdata.append(State::Sx);
+ } else {
+ hw_wdata.append(wport.data[bit.bit]);
+ }
+ }
+ for (int i = 0; i < GetSize(port_swz.bits[rd]); i += effective_byte) {
+ auto &bit = port_swz.bits[rd][i];
+ if (!bit.valid) {
+ hw_wren.append(State::S0);
+ } else {
+ hw_wren.append(big_wren[bit.mux_idx][bit.bit]);
+ }
+ }
+ cell->setPort(stringf("\\PORT_%s_WR_DATA", name), hw_wdata);
+ if (pdef.wrbe_separate) {
+ // TODO make some use of it
+ SigSpec en = mem.module->ReduceOr(NEW_ID, hw_wren);
+ cell->setPort(stringf("\\PORT_%s_WR_EN", name), en);
+ cell->setPort(stringf("\\PORT_%s_WR_BE", name), hw_wren);
+ if (cfg.def->width_mode != WidthMode::Single)
+ cell->setParam(stringf("\\PORT_%s_WR_BE_WIDTH", name), GetSize(hw_wren));
+ } else {
+ cell->setPort(stringf("\\PORT_%s_WR_EN", name), hw_wren);
+ if (cfg.def->byte != 0 && cfg.def->width_mode != WidthMode::Single)
+ cell->setParam(stringf("\\PORT_%s_WR_EN_WIDTH", name), GetSize(hw_wren));
+ }
+ }
+ } else {
+ for (auto cell: cells) {
+ cell->setPort(stringf("\\PORT_%s_WR_DATA", name), Const(State::Sx, width));
+ SigSpec hw_wren = Const(State::S0, width / effective_byte);
+ if (pdef.wrbe_separate) {
+ cell->setPort(stringf("\\PORT_%s_WR_EN", name), State::S0);
+ cell->setPort(stringf("\\PORT_%s_WR_BE", name), hw_wren);
+ cell->setParam(stringf("\\PORT_%s_WR_BE_WIDTH", name), GetSize(hw_wren));
+ } else {
+ cell->setPort(stringf("\\PORT_%s_WR_EN", name), hw_wren);
+ if (cfg.def->byte != 0)
+ cell->setParam(stringf("\\PORT_%s_WR_EN_WIDTH", name), GetSize(hw_wren));
+ }
+ }
+ }
+ }
+
+ // Read part.
+ if (pdef.kind != PortKind::Sw) {
+ int width = cfg.def->dbits[hw_rd_wide_log2];
+ if (rpidx != -1) {
+ auto &rport = mem.rd_ports[rpidx];
+ auto &rpcfg = cfg.rd_ports[rpidx];
+ Swizzle port_swz = gen_swizzle(mem, cfg, rport.wide_log2, hw_rd_wide_log2);
+ std::vector<SigSpec> big_rdata = generate_mux(mem, rpidx, port_swz);
+ for (int rd = 0; rd < cfg.repl_d; rd++) {
+ auto cell = cells[rd];
+ if (pdef.kind == PortKind::Sr || pdef.kind == PortKind::Srsw) {
+ if (pdef.rd_en)
+ cell->setPort(stringf("\\PORT_%s_RD_EN", name), rpcfg.rd_en_to_clk_en ? State::S1 : rport.en);
+ if (pdef.rdarstval != ResetValKind::None)
+ cell->setPort(stringf("\\PORT_%s_RD_ARST", name), rport.arst);
+ if (pdef.rdsrstval != ResetValKind::None)
+ cell->setPort(stringf("\\PORT_%s_RD_SRST", name), rport.srst);
+ if (pdef.rdinitval == ResetValKind::Any || pdef.rdinitval == ResetValKind::NoUndef) {
+ Const val = rport.init_value;
+ if (pdef.rdarstval == ResetValKind::Init && rport.arst != State::S0) {
+ log_assert(val.is_fully_undef() || val == rport.arst_value);
+ val = rport.arst_value;
+ }
+ if (pdef.rdsrstval == ResetValKind::Init && rport.srst != State::S0) {
+ log_assert(val.is_fully_undef() || val == rport.srst_value);
+ val = rport.srst_value;
+ }
+ std::vector<State> hw_val;
+ for (auto &bit : port_swz.bits[rd]) {
+ if (!bit.valid) {
+ hw_val.push_back(State::Sx);
+ } else {
+ hw_val.push_back(val.bits[bit.bit]);
+ }
+ }
+ if (pdef.rdinitval == ResetValKind::NoUndef)
+ clean_undef(hw_val);
+ cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), hw_val);
+ }
+ if (pdef.rdarstval == ResetValKind::Any || pdef.rdarstval == ResetValKind::NoUndef) {
+ std::vector<State> hw_val;
+ for (auto &bit : port_swz.bits[rd]) {
+ if (!bit.valid) {
+ hw_val.push_back(State::Sx);
+ } else {
+ hw_val.push_back(rport.arst_value.bits[bit.bit]);
+ }
+ }
+ if (pdef.rdarstval == ResetValKind::NoUndef)
+ clean_undef(hw_val);
+ cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), hw_val);
+ }
+ if (pdef.rdsrstval == ResetValKind::Any || pdef.rdsrstval == ResetValKind::NoUndef) {
+ std::vector<State> hw_val;
+ for (auto &bit : port_swz.bits[rd]) {
+ if (!bit.valid) {
+ hw_val.push_back(State::Sx);
+ } else {
+ hw_val.push_back(rport.srst_value.bits[bit.bit]);
+ }
+ }
+ if (pdef.rdsrstval == ResetValKind::NoUndef)
+ clean_undef(hw_val);
+ cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), hw_val);
+ }
+ }
+ SigSpec hw_rdata = mem.module->addWire(NEW_ID, width);
+ cell->setPort(stringf("\\PORT_%s_RD_DATA", name), hw_rdata);
+ SigSpec lhs;
+ SigSpec rhs;
+ for (int i = 0; i < GetSize(hw_rdata); i++) {
+ auto &bit = port_swz.bits[rd][i];
+ if (bit.valid) {
+ lhs.append(big_rdata[bit.mux_idx][bit.bit]);
+ rhs.append(hw_rdata[i]);
+ }
+ }
+ mem.module->connect(lhs, rhs);
+ }
+ } else {
+ for (auto cell: cells) {
+ if (pdef.kind == PortKind::Sr || pdef.kind == PortKind::Srsw) {
+ if (pdef.rd_en)
+ cell->setPort(stringf("\\PORT_%s_RD_EN", name), State::S0);
+ if (pdef.rdarstval != ResetValKind::None)
+ cell->setPort(stringf("\\PORT_%s_RD_ARST", name), State::S0);
+ if (pdef.rdsrstval != ResetValKind::None)
+ cell->setPort(stringf("\\PORT_%s_RD_SRST", name), State::S0);
+ if (pdef.rdinitval == ResetValKind::Any)
+ cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), Const(State::Sx, width));
+ else if (pdef.rdinitval == ResetValKind::NoUndef)
+ cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), Const(State::S0, width));
+ if (pdef.rdarstval == ResetValKind::Any)
+ cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), Const(State::Sx, width));
+ else if (pdef.rdarstval == ResetValKind::NoUndef)
+ cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), Const(State::S0, width));
+ if (pdef.rdsrstval == ResetValKind::Any)
+ cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), Const(State::Sx, width));
+ else if (pdef.rdsrstval == ResetValKind::NoUndef)
+ cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), Const(State::S0, width));
+ }
+ SigSpec hw_rdata = mem.module->addWire(NEW_ID, width);
+ cell->setPort(stringf("\\PORT_%s_RD_DATA", name), hw_rdata);
+ }
+ }
+ }
+}
+
+void MemMapping::emit(const MemConfig &cfg) {
+ log("mapping memory %s.%s via %s\n", log_id(mem.module->name), log_id(mem.memid), log_id(cfg.def->id));
+ // First, handle emulations.
+ if (cfg.emu_read_first)
+ mem.emulate_read_first(&worker.initvals);
+ for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
+ auto &pcfg = cfg.rd_ports[pidx];
+ auto &port = mem.rd_ports[pidx];
+ if (pcfg.emu_sync)
+ mem.extract_rdff(pidx, &worker.initvals);
+ else if (pcfg.emu_en)
+ mem.emulate_rden(pidx, &worker.initvals);
+ else {
+ if (pcfg.emu_srst_en_prio) {
+ if (port.ce_over_srst)
+ mem.emulate_rd_ce_over_srst(pidx);
+ else
+ mem.emulate_rd_srst_over_ce(pidx);
+ }
+ mem.emulate_reset(pidx, pcfg.emu_init, pcfg.emu_arst, pcfg.emu_srst, &worker.initvals);
+ }
+ }
+ for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) {
+ auto &pcfg = cfg.wr_ports[pidx];
+ for (int opidx: pcfg.emu_prio) {
+ mem.emulate_priority(opidx, pidx, &worker.initvals);
+ }
+ }
+ for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
+ auto &port = mem.rd_ports[pidx];
+ auto &pcfg = cfg.rd_ports[pidx];
+ for (int opidx: pcfg.emu_trans) {
+ // The port may no longer be transparent due to transparency being
+ // nuked as part of emu_sync or emu_prio.
+ if (port.transparency_mask[opidx])
+ mem.emulate_transparency(opidx, pidx, &worker.initvals);
+ }
+ }
+
+ // tgt (repl, port group, port) -> mem (wr port, rd port), where -1 means no port.
+ std::vector<std::vector<std::vector<std::pair<int, int>>>> ports(cfg.repl_port);
+ for (int i = 0; i < cfg.repl_port; i++)
+ ports[i].resize(cfg.def->port_groups.size());
+ for (int i = 0; i < GetSize(cfg.wr_ports); i++) {
+ auto &pcfg = cfg.wr_ports[i];
+ for (int j = 0; j < cfg.repl_port; j++) {
+ if (j == 0) {
+ ports[j][pcfg.port_group].push_back({i, pcfg.rd_port});
+ } else {
+ ports[j][pcfg.port_group].push_back({i, -1});
+ }
+ }
+ }
+ for (int i = 0; i < GetSize(cfg.rd_ports); i++) {
+ auto &pcfg = cfg.rd_ports[i];
+ if (pcfg.wr_port != -1)
+ continue;
+ auto &pg = cfg.def->port_groups[pcfg.port_group];
+ int j = 0;
+ while (GetSize(ports[j][pcfg.port_group]) >= GetSize(pg.names))
+ j++;
+ ports[j][pcfg.port_group].push_back({-1, i});
+ }
+
+ Swizzle init_swz = gen_swizzle(mem, cfg, 0, GetSize(cfg.def->dbits) - 1);
+ Const init_data = mem.get_init_data();
+
+ std::vector<int> hw_addr_swizzle;
+ for (int i = 0; i < cfg.base_width_log2; i++)
+ hw_addr_swizzle.push_back(-1);
+ for (int i = 0; i < init_swz.addr_shift; i++)
+ if (!(cfg.emu_wide_mask & 1 << i))
+ hw_addr_swizzle.push_back(i);
+ log_assert(GetSize(hw_addr_swizzle) == cfg.def->abits);
+
+ for (int rp = 0; rp < cfg.repl_port; rp++) {
+ std::vector<Cell *> cells;
+ for (int rd = 0; rd < cfg.repl_d; rd++) {
+ Cell *cell = mem.module->addCell(stringf("%s.%d.%d", mem.memid.c_str(), rp, rd), cfg.def->id);
+ if (cfg.def->width_mode == WidthMode::Global)
+ cell->setParam(ID::WIDTH, cfg.def->dbits[cfg.base_width_log2]);
+ if (cfg.def->widthscale) {
+ std::vector<State> val;
+ for (auto &bit: init_swz.bits[rd])
+ val.push_back(bit.valid ? State::S1 : State::S0);
+ cell->setParam(ID::BITS_USED, val);
+ }
+ for (auto &it: cfg.def->options)
+ cell->setParam(stringf("\\OPTION_%s", it.first.c_str()), it.second);
+ for (int i = 0; i < GetSize(cfg.def->shared_clocks); i++) {
+ auto &cdef = cfg.def->shared_clocks[i];
+ auto &ccfg = cfg.shared_clocks[i];
+ if (cdef.anyedge) {
+ cell->setParam(stringf("\\CLK_%s_POL", cdef.name.c_str()), ccfg.used ? ccfg.polarity : true);
+ cell->setPort(stringf("\\CLK_%s", cdef.name.c_str()), ccfg.used ? ccfg.clk : State::S0);
+ } else {
+ SigSpec sig = ccfg.used ? ccfg.clk : State::S0;
+ if (ccfg.used && ccfg.invert)
+ sig = mem.module->Not(NEW_ID, sig);
+ cell->setPort(stringf("\\CLK_%s", cdef.name.c_str()), sig);
+ }
+ }
+ if (cfg.def->init == MemoryInitKind::Any || cfg.def->init == MemoryInitKind::NoUndef) {
+ std::vector<State> initval;
+ for (int hwa = 0; hwa < (1 << cfg.def->abits); hwa += 1 << (GetSize(cfg.def->dbits) - 1)) {
+ for (auto &bit: init_swz.bits[rd]) {
+ if (!bit.valid) {
+ initval.push_back(State::Sx);
+ } else {
+ int addr = bit.addr;
+ for (int i = GetSize(cfg.def->dbits) - 1; i < cfg.def->abits; i++)
+ if (hwa & 1 << i)
+ addr += 1 << hw_addr_swizzle[i];
+ if (addr >= mem.start_offset && addr < mem.start_offset + mem.size)
+ initval.push_back(init_data.bits[(addr - mem.start_offset) * mem.width + bit.bit]);
+ else
+ initval.push_back(State::Sx);
+ }
+ }
+ }
+ if (cfg.def->init == MemoryInitKind::NoUndef)
+ clean_undef(initval);
+ cell->setParam(ID::INIT, initval);
+ }
+ cells.push_back(cell);
+ }
+ for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) {
+ auto &pg = cfg.def->port_groups[pgi];
+ for (int pi = 0; pi < GetSize(pg.names); pi++) {
+ bool used = pi < GetSize(ports[rp][pgi]);
+ bool used_r = false;
+ bool used_w = false;
+ if (used) {
+ auto &pd = ports[rp][pgi][pi];
+ const PortVariant *pdef;
+ if (pd.first != -1)
+ pdef = cfg.wr_ports[pd.first].def;
+ else
+ pdef = cfg.rd_ports[pd.second].def;
+ used_w = pd.first != -1;
+ used_r = pd.second != -1;
+ emit_port(cfg, cells, *pdef, pg.names[pi].c_str(), pd.first, pd.second, hw_addr_swizzle);
+ } else {
+ emit_port(cfg, cells, pg.variants[0], pg.names[pi].c_str(), -1, -1, hw_addr_swizzle);
+ }
+ if (pg.optional)
+ for (auto cell: cells)
+ cell->setParam(stringf("\\PORT_%s_USED", pg.names[pi].c_str()), used);
+ if (pg.optional_rw)
+ for (auto cell: cells) {
+ cell->setParam(stringf("\\PORT_%s_RD_USED", pg.names[pi].c_str()), used_r);
+ cell->setParam(stringf("\\PORT_%s_WR_USED", pg.names[pi].c_str()), used_w);
+ }
+ }
+ }
+ }
+ mem.remove();
+}
+
+struct MemoryLibMapPass : public Pass {
+ MemoryLibMapPass() : Pass("memory_libmap", "map memories to cells") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" memory_libmap -lib <library_file> [-D <condition>] [selection]\n");
+ log("\n");
+ log("This pass takes a description of available RAM cell types and maps\n");
+ log("all selected memories to one of them, or leaves them to be mapped to FFs.\n");
+ log("\n");
+ log(" -lib <library_file>\n");
+ log(" Selects a library file containing RAM cell definitions. This option\n");
+ log(" can be passed more than once to select multiple libraries.\n");
+ log(" See passes/memory/memlib.md for description of the library format.\n");
+ log("\n");
+ log(" -D <condition>\n");
+ log(" Enables a condition that can be checked within the library file\n");
+ log(" to eg. select between slightly different hardware variants.\n");
+ log(" This option can be passed any number of times.\n");
+ log("\n");
+ log(" -logic-cost-rom <num>\n");
+ log(" -logic-cost-ram <num>\n");
+ log(" Sets the cost of a single bit for memory lowered to soft logic.\n");
+ log("\n");
+ log(" -no-auto-distributed\n");
+ log(" -no-auto-block\n");
+ log(" -no-auto-huge\n");
+ log(" Disables automatic mapping of given kind of RAMs. Manual mapping\n");
+ log(" (using ram_style or other attributes) is still supported.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ std::vector<std::string> lib_files;
+ pool<std::string> defines;
+ PassOptions opts;
+ opts.no_auto_distributed = false;
+ opts.no_auto_block = false;
+ opts.no_auto_huge = false;
+ opts.logic_cost_ram = 1.0;
+ opts.logic_cost_rom = 1.0/16.0;
+ log_header(design, "Executing MEMORY_LIBMAP pass (mapping memories to cells).\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-lib" && argidx+1 < args.size()) {
+ lib_files.push_back(args[++argidx]);
+ continue;
+ }
+ if (args[argidx] == "-D" && argidx+1 < args.size()) {
+ defines.insert(args[++argidx]);
+ continue;
+ }
+ if (args[argidx] == "-no-auto-distributed") {
+ opts.no_auto_distributed = true;
+ continue;
+ }
+ if (args[argidx] == "-no-auto-block") {
+ opts.no_auto_block = true;
+ continue;
+ }
+ if (args[argidx] == "-no-auto-huge") {
+ opts.no_auto_huge = true;
+ continue;
+ }
+ if (args[argidx] == "-logic-cost-rom" && argidx+1 < args.size()) {
+ opts.logic_cost_rom = strtod(args[++argidx].c_str(), nullptr);
+ continue;
+ }
+ if (args[argidx] == "-logic-cost-ram" && argidx+1 < args.size()) {
+ opts.logic_cost_ram = strtod(args[++argidx].c_str(), nullptr);
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ Library lib = parse_library(lib_files, defines);
+
+ for (auto module : design->selected_modules()) {
+ MapWorker worker(module);
+ auto mems = Mem::get_selected_memories(module);
+ for (auto &mem : mems)
+ {
+ MemMapping map(worker, mem, lib, opts);
+ int idx = -1;
+ int best = map.logic_cost;
+ if (!map.logic_ok) {
+ if (map.cfgs.empty())
+ log_error("no valid mapping found for memory %s.%s\n", log_id(module->name), log_id(mem.memid));
+ idx = 0;
+ best = map.cfgs[0].cost;
+ }
+ for (int i = 0; i < GetSize(map.cfgs); i++) {
+ if (map.cfgs[i].cost < best) {
+ idx = i;
+ best = map.cfgs[i].cost;
+ }
+ }
+ if (idx == -1) {
+ log("using FF mapping for memory %s.%s\n", log_id(module->name), log_id(mem.memid));
+ } else {
+ map.emit(map.cfgs[idx]);
+ }
+ }
+ }
+ }
+} MemoryLibMapPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/memory/memory_share.cc b/passes/memory/memory_share.cc
index ceea725d8..5cb11d62b 100644
--- a/passes/memory/memory_share.cc
+++ b/passes/memory/memory_share.cc
@@ -82,6 +82,11 @@ struct MemoryShareWorker
log("Consolidating read ports of memory %s.%s by address:\n", log_id(module), log_id(mem.memid));
bool changed = false;
+ int abits = 0;
+ for (auto &port: mem.rd_ports) {
+ if (GetSize(port.addr) > abits)
+ abits = GetSize(port.addr);
+ }
for (int i = 0; i < GetSize(mem.rd_ports); i++)
{
auto &port1 = mem.rd_ports[i];
@@ -114,6 +119,8 @@ struct MemoryShareWorker
int wide_log2 = std::max(port1.wide_log2, port2.wide_log2);
SigSpec addr1 = sigmap_xmux(port1.addr);
SigSpec addr2 = sigmap_xmux(port2.addr);
+ addr1.extend_u0(abits);
+ addr2.extend_u0(abits);
if (GetSize(addr1) <= wide_log2)
continue;
if (GetSize(addr2) <= wide_log2)
@@ -192,6 +199,11 @@ struct MemoryShareWorker
log("Consolidating write ports of memory %s.%s by address:\n", log_id(module), log_id(mem.memid));
bool changed = false;
+ int abits = 0;
+ for (auto &port: mem.wr_ports) {
+ if (GetSize(port.addr) > abits)
+ abits = GetSize(port.addr);
+ }
for (int i = 0; i < GetSize(mem.wr_ports); i++)
{
auto &port1 = mem.wr_ports[i];
@@ -216,6 +228,8 @@ struct MemoryShareWorker
int wide_log2 = std::max(port1.wide_log2, port2.wide_log2);
SigSpec addr1 = sigmap_xmux(port1.addr);
SigSpec addr2 = sigmap_xmux(port2.addr);
+ addr1.extend_u0(abits);
+ addr2.extend_u0(abits);
if (GetSize(addr1) <= wide_log2)
continue;
if (GetSize(addr2) <= wide_log2)
@@ -541,7 +555,7 @@ struct MemorySharePass : public Pass {
}
break;
}
- extra_args(args, 1, design);
+ extra_args(args, argidx, design);
MemoryShareWorker msw(design, flag_widen, flag_sat);
for (auto module : design->selected_modules())
diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc
index 4e52ad8da..76bf8a84e 100644
--- a/passes/opt/Makefile.inc
+++ b/passes/opt/Makefile.inc
@@ -19,6 +19,7 @@ OBJS += passes/opt/opt_demorgan.o
OBJS += passes/opt/rmports.o
OBJS += passes/opt/opt_lut.o
OBJS += passes/opt/opt_lut_ins.o
+OBJS += passes/opt/opt_ffinv.o
OBJS += passes/opt/pmux2shiftx.o
OBJS += passes/opt/muxpack.o
endif
diff --git a/passes/opt/opt.cc b/passes/opt/opt.cc
index c3e418c07..dc88563c2 100644
--- a/passes/opt/opt.cc
+++ b/passes/opt/opt.cc
@@ -114,6 +114,7 @@ struct OptPass : public Pass {
if (args[argidx] == "-keepdc") {
opt_expr_args += " -keepdc";
opt_dff_args += " -keepdc";
+ opt_merge_args += " -keepdc";
continue;
}
if (args[argidx] == "-nodffe") {
diff --git a/passes/opt/opt_dff.cc b/passes/opt/opt_dff.cc
index 98b66dee3..0ad4acec2 100644
--- a/passes/opt/opt_dff.cc
+++ b/passes/opt/opt_dff.cc
@@ -58,13 +58,10 @@ struct OptDffWorker
typedef std::pair<RTLIL::SigBit, bool> ctrl_t;
typedef std::set<ctrl_t> ctrls_t;
- ModWalker modwalker;
- QuickConeSat qcsat;
-
// Used as a queue.
std::vector<Cell *> dff_cells;
- OptDffWorker(const OptDffOptions &opt, Module *mod) : opt(opt), module(mod), sigmap(mod), initvals(&sigmap, mod), modwalker(module->design, module), qcsat(modwalker) {
+ OptDffWorker(const OptDffOptions &opt, Module *mod) : opt(opt), module(mod), sigmap(mod), initvals(&sigmap, mod) {
// Gathering two kinds of information here for every sigmapped SigBit:
//
// - bitusers: how many users it has (muxes will only be merged into FFs if this is 1, making the FF the only user)
@@ -557,7 +554,7 @@ struct OptDffWorker
// The D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver).
log("Handling D = Q on %s (%s) from module %s (removing D path).\n",
log_id(cell), log_id(cell->type), log_id(module));
- ff.has_clk = ff.has_ce = false;
+ ff.has_gclk = ff.has_clk = ff.has_ce = false;
changed = true;
}
}
@@ -569,100 +566,6 @@ struct OptDffWorker
changed = true;
}
- // Now check if any bit can be replaced by a constant.
- pool<int> removed_sigbits;
- for (int i = 0; i < ff.width; i++) {
- State val = ff.val_init[i];
- if (ff.has_arst)
- val = combine_const(val, ff.val_arst[i]);
- if (ff.has_srst)
- val = combine_const(val, ff.val_srst[i]);
- if (ff.has_sr) {
- if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1))
- val = combine_const(val, State::S0);
- if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1))
- val = combine_const(val, State::S1);
- }
- if (val == State::Sm)
- continue;
- if (ff.has_clk || ff.has_gclk) {
- if (!ff.sig_d[i].wire) {
- val = combine_const(val, ff.sig_d[i].data);
- if (val == State::Sm)
- continue;
- } else {
- if (!opt.sat)
- continue;
- // For each register bit, try to prove that it cannot change from the initial value. If so, remove it
- if (!modwalker.has_drivers(ff.sig_d.extract(i)))
- continue;
- if (val != State::S0 && val != State::S1)
- continue;
-
- int init_sat_pi = qcsat.importSigBit(val);
- int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]);
- int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]);
-
- qcsat.prepare();
-
- // Try to find out whether the register bit can change under some circumstances
- bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi)));
-
- // If the register bit cannot change, we can replace it with a constant
- if (counter_example_found)
- continue;
- }
- }
- if (ff.has_aload) {
- if (!ff.sig_ad[i].wire) {
- val = combine_const(val, ff.sig_ad[i].data);
- if (val == State::Sm)
- continue;
- } else {
- if (!opt.sat)
- continue;
- // For each register bit, try to prove that it cannot change from the initial value. If so, remove it
- if (!modwalker.has_drivers(ff.sig_ad.extract(i)))
- continue;
- if (val != State::S0 && val != State::S1)
- continue;
-
- int init_sat_pi = qcsat.importSigBit(val);
- int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]);
- int d_sat_pi = qcsat.importSigBit(ff.sig_ad[i]);
-
- qcsat.prepare();
-
- // Try to find out whether the register bit can change under some circumstances
- bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi)));
-
- // If the register bit cannot change, we can replace it with a constant
- if (counter_example_found)
- continue;
- }
- }
- log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0,
- i, log_id(cell), log_id(cell->type), log_id(module));
-
- initvals.remove_init(ff.sig_q[i]);
- module->connect(ff.sig_q[i], val);
- removed_sigbits.insert(i);
- }
- if (!removed_sigbits.empty()) {
- std::vector<int> keep_bits;
- for (int i = 0; i < ff.width; i++)
- if (!removed_sigbits.count(i))
- keep_bits.push_back(i);
- if (keep_bits.empty()) {
- module->remove(cell);
- did_something = true;
- continue;
- }
- ff = ff.slice(keep_bits);
- ff.cell = cell;
- changed = true;
- }
-
// The cell has been simplified as much as possible already. Now try to spice it up with enables / sync resets.
if (ff.has_clk) {
if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_ce || ff.ce_over_srst) && !opt.nosdff) {
@@ -818,6 +721,115 @@ struct OptDffWorker
}
return did_something;
}
+
+ bool run_constbits() {
+ ModWalker modwalker(module->design, module);
+ QuickConeSat qcsat(modwalker);
+
+ // Run as a separate sub-pass, so that we don't mutate (non-FF) cells under ModWalker.
+ bool did_something = false;
+ for (auto cell : module->selected_cells()) {
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type))
+ continue;
+ FfData ff(&initvals, cell);
+
+ // Now check if any bit can be replaced by a constant.
+ pool<int> removed_sigbits;
+ for (int i = 0; i < ff.width; i++) {
+ State val = ff.val_init[i];
+ if (ff.has_arst)
+ val = combine_const(val, ff.val_arst[i]);
+ if (ff.has_srst)
+ val = combine_const(val, ff.val_srst[i]);
+ if (ff.has_sr) {
+ if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1))
+ val = combine_const(val, State::S0);
+ if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1))
+ val = combine_const(val, State::S1);
+ }
+ if (val == State::Sm)
+ continue;
+ if (ff.has_clk || ff.has_gclk) {
+ if (!ff.sig_d[i].wire) {
+ val = combine_const(val, ff.sig_d[i].data);
+ if (val == State::Sm)
+ continue;
+ } else {
+ if (!opt.sat)
+ continue;
+ // For each register bit, try to prove that it cannot change from the initial value. If so, remove it
+ if (!modwalker.has_drivers(ff.sig_d.extract(i)))
+ continue;
+ if (val != State::S0 && val != State::S1)
+ continue;
+
+ int init_sat_pi = qcsat.importSigBit(val);
+ int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]);
+ int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]);
+
+ qcsat.prepare();
+
+ // Try to find out whether the register bit can change under some circumstances
+ bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi)));
+
+ // If the register bit cannot change, we can replace it with a constant
+ if (counter_example_found)
+ continue;
+ }
+ }
+ if (ff.has_aload) {
+ if (!ff.sig_ad[i].wire) {
+ val = combine_const(val, ff.sig_ad[i].data);
+ if (val == State::Sm)
+ continue;
+ } else {
+ if (!opt.sat)
+ continue;
+ // For each register bit, try to prove that it cannot change from the initial value. If so, remove it
+ if (!modwalker.has_drivers(ff.sig_ad.extract(i)))
+ continue;
+ if (val != State::S0 && val != State::S1)
+ continue;
+
+ int init_sat_pi = qcsat.importSigBit(val);
+ int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]);
+ int d_sat_pi = qcsat.importSigBit(ff.sig_ad[i]);
+
+ qcsat.prepare();
+
+ // Try to find out whether the register bit can change under some circumstances
+ bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi)));
+
+ // If the register bit cannot change, we can replace it with a constant
+ if (counter_example_found)
+ continue;
+ }
+ }
+ log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0,
+ i, log_id(cell), log_id(cell->type), log_id(module));
+
+ initvals.remove_init(ff.sig_q[i]);
+ module->connect(ff.sig_q[i], val);
+ removed_sigbits.insert(i);
+ }
+ if (!removed_sigbits.empty()) {
+ std::vector<int> keep_bits;
+ for (int i = 0; i < ff.width; i++)
+ if (!removed_sigbits.count(i))
+ keep_bits.push_back(i);
+ if (keep_bits.empty()) {
+ module->remove(cell);
+ did_something = true;
+ continue;
+ }
+ ff = ff.slice(keep_bits);
+ ff.cell = cell;
+ ff.emit();
+ did_something = true;
+ }
+ }
+ return did_something;
+ }
};
struct OptDffPass : public Pass {
@@ -894,6 +906,8 @@ struct OptDffPass : public Pass {
OptDffWorker worker(opt, mod);
if (worker.run())
did_something = true;
+ if (worker.run_constbits())
+ did_something = true;
}
if (did_something)
diff --git a/passes/opt/opt_ffinv.cc b/passes/opt/opt_ffinv.cc
new file mode 100644
index 000000000..5d989dafd
--- /dev/null
+++ b/passes/opt/opt_ffinv.cc
@@ -0,0 +1,265 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2022 Marcelina Koƛcielnicka <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"
+#include "kernel/modtools.h"
+#include "kernel/ffinit.h"
+#include "kernel/ff.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct OptFfInvWorker
+{
+ int count = 0;
+ RTLIL::Module *module;
+ ModIndex index;
+ FfInitVals initvals;
+
+ // Case 1:
+ // - FF is driven by inverter
+ // - ... which has no other users
+ // - all users of FF are LUTs
+ bool push_d_inv(FfData &ff) {
+ if (index.query_is_input(ff.sig_d))
+ return false;
+ if (index.query_is_output(ff.sig_d))
+ return false;
+ auto d_ports = index.query_ports(ff.sig_d);
+ if (d_ports.size() != 2)
+ return false;
+ Cell *d_inv = nullptr;
+ for (auto &port: d_ports) {
+ if (port.cell == ff.cell && port.port == ID::D)
+ continue;
+ if (port.port != ID::Y)
+ return false;
+ if (port.cell->type.in(ID($not), ID($_NOT_))) {
+ // OK
+ } else if (port.cell->type.in(ID($lut))) {
+ if (port.cell->getParam(ID::WIDTH) != 1)
+ return false;
+ if (port.cell->getParam(ID::LUT).as_int() != 1)
+ return false;
+ } else {
+ return false;
+ }
+ log_assert(d_inv == nullptr);
+ d_inv = port.cell;
+ }
+
+ if (index.query_is_output(ff.sig_q))
+ return false;
+ auto q_ports = index.query_ports(ff.sig_q);
+ pool<Cell *> q_luts;
+ for (auto &port: q_ports) {
+ if (port.cell == ff.cell && port.port == ID::Q)
+ continue;
+ if (port.cell == d_inv)
+ return false;
+ if (port.port != ID::A)
+ return false;
+ if (!port.cell->type.in(ID($not), ID($_NOT_), ID($lut)))
+ return false;
+ q_luts.insert(port.cell);
+ }
+
+ ff.flip_rst_bits({0});
+ ff.sig_d = d_inv->getPort(ID::A);
+
+ for (Cell *lut: q_luts) {
+ if (lut->type == ID($lut)) {
+ int flip_mask = 0;
+ SigSpec sig_a = lut->getPort(ID::A);
+ for (int i = 0; i < GetSize(sig_a); i++) {
+ if (index.sigmap(sig_a[i]) == index.sigmap(ff.sig_q)) {
+ flip_mask |= 1 << i;
+ }
+ }
+ Const mask = lut->getParam(ID::LUT);
+ Const new_mask;
+ for (int j = 0; j < (1 << GetSize(sig_a)); j++) {
+ new_mask.bits.push_back(mask.bits[j ^ flip_mask]);
+ }
+ if (GetSize(sig_a) == 1 && new_mask.as_int() == 2) {
+ module->connect(lut->getPort(ID::Y), ff.sig_q);
+ module->remove(lut);
+ } else {
+ lut->setParam(ID::LUT, new_mask);
+ }
+ } else {
+ // it was an inverter
+ module->connect(lut->getPort(ID::Y), ff.sig_q);
+ module->remove(lut);
+ }
+ }
+
+ ff.emit();
+ return true;
+ }
+
+ // Case 2:
+ // - FF is driven by LUT
+ // - ... which has no other users
+ // - FF has one user
+ // - ... which is an inverter
+ bool push_q_inv(FfData &ff) {
+ if (index.query_is_input(ff.sig_d))
+ return false;
+ if (index.query_is_output(ff.sig_d))
+ return false;
+
+ Cell *d_lut = nullptr;
+ auto d_ports = index.query_ports(ff.sig_d);
+ if (d_ports.size() != 2)
+ return false;
+ for (auto &port: d_ports) {
+ if (port.cell == ff.cell && port.port == ID::D)
+ continue;
+ if (port.port != ID::Y)
+ return false;
+ if (!port.cell->type.in(ID($not), ID($_NOT_), ID($lut)))
+ return false;
+ log_assert(d_lut == nullptr);
+ d_lut = port.cell;
+ }
+
+ if (index.query_is_output(ff.sig_q))
+ return false;
+ auto q_ports = index.query_ports(ff.sig_q);
+ if (q_ports.size() != 2)
+ return false;
+ Cell *q_inv = nullptr;
+ for (auto &port: q_ports) {
+ if (port.cell == ff.cell && port.port == ID::Q)
+ continue;
+ if (port.cell == d_lut)
+ return false;
+ if (port.port != ID::A)
+ return false;
+ if (port.cell->type.in(ID($not), ID($_NOT_))) {
+ // OK
+ } else if (port.cell->type.in(ID($lut))) {
+ if (port.cell->getParam(ID::WIDTH) != 1)
+ return false;
+ if (port.cell->getParam(ID::LUT).as_int() != 1)
+ return false;
+ } else {
+ return false;
+ }
+ log_assert(q_inv == nullptr);
+ q_inv = port.cell;
+ }
+
+ ff.flip_rst_bits({0});
+ ff.sig_q = q_inv->getPort(ID::Y);
+ module->remove(q_inv);
+
+ if (d_lut->type == ID($lut)) {
+ Const mask = d_lut->getParam(ID::LUT);
+ Const new_mask;
+ for (int i = 0; i < GetSize(mask); i++) {
+ if (mask.bits[i] == State::S0)
+ new_mask.bits.push_back(State::S1);
+ else
+ new_mask.bits.push_back(State::S0);
+ }
+ d_lut->setParam(ID::LUT, new_mask);
+ if (d_lut->getParam(ID::WIDTH) == 1 && new_mask.as_int() == 2) {
+ module->connect(ff.sig_d, d_lut->getPort(ID::A));
+ module->remove(d_lut);
+ }
+ } else {
+ // it was an inverter
+ module->connect(ff.sig_d, d_lut->getPort(ID::A));
+ module->remove(d_lut);
+ }
+
+ ff.emit();
+ return true;
+ }
+
+ OptFfInvWorker(RTLIL::Module *module) :
+ module(module), index(module), initvals(&index.sigmap, module)
+ {
+ log("Discovering LUTs.\n");
+
+ std::vector<Cell *> ffs;
+
+ for (Cell *cell : module->selected_cells())
+ if (RTLIL::builtin_ff_cell_types().count(cell->type))
+ ffs.push_back(cell);
+
+ for (Cell *cell : ffs) {
+ FfData ff(&initvals, cell);
+ if (ff.has_sr)
+ continue;
+ if (!ff.has_clk)
+ continue;
+ if (ff.has_aload)
+ continue;
+ if (ff.width != 1)
+ continue;
+
+ if (push_d_inv(ff)) {
+ count++;
+ } else if (push_q_inv(ff)) {
+ count++;
+ }
+ }
+ }
+};
+
+struct OptFfInvPass : public Pass {
+ OptFfInvPass() : Pass("opt_ffinv", "push inverters through FFs") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" opt_ffinv [selection]\n");
+ log("\n");
+ log("This pass pushes inverters to the other side of a FF when they can be merged\n");
+ log("into LUTs on the other side.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ log_header(design, "Executing OPT_FFINV pass (push inverters through FFs).\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++)
+ {
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ int total_count = 0;
+ for (auto module : design->selected_modules())
+ {
+ OptFfInvWorker worker(module);
+ total_count += worker.count;
+ }
+ if (total_count)
+ design->scratchpad_set_bool("opt.did_something", true);
+ log("Pushed %d inverters.\n", total_count);
+ }
+} OptFfInvPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/opt/opt_mem.cc b/passes/opt/opt_mem.cc
index edadf2c7b..885b6f97d 100644
--- a/passes/opt/opt_mem.cc
+++ b/passes/opt/opt_mem.cc
@@ -20,6 +20,7 @@
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include "kernel/mem.h"
+#include "kernel/ff.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@@ -54,31 +55,160 @@ struct OptMemPass : public Pass {
SigMap sigmap(module);
FfInitVals initvals(&sigmap, module);
for (auto &mem : Mem::get_selected_memories(module)) {
+ std::vector<bool> always_0(mem.width, true);
+ std::vector<bool> always_1(mem.width, true);
bool changed = false;
for (auto &port : mem.wr_ports) {
if (port.en.is_fully_zero()) {
port.removed = true;
changed = true;
total_count++;
+ } else {
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++) {
+ for (int i = 0; i < mem.width; i++) {
+ int bit = sub * mem.width + i;
+ if (port.en[bit] != State::S0) {
+ if (port.data[bit] != State::Sx && port.data[bit] != State::S0) {
+ always_0[i] = false;
+ }
+ if (port.data[bit] != State::Sx && port.data[bit] != State::S1) {
+ always_1[i] = false;
+ }
+ } else {
+ if (port.data[bit] != State::Sx) {
+ port.data[bit] = State::Sx;
+ changed = true;
+ total_count++;
+ }
+ }
+ }
+ }
}
}
- if (changed) {
- mem.emit();
+ for (auto &init : mem.inits) {
+ for (int i = 0; i < GetSize(init.data); i++) {
+ State bit = init.data.bits[i];
+ int lane = i % mem.width;
+ if (bit != State::Sx && bit != State::S0) {
+ always_0[lane] = false;
+ }
+ if (bit != State::Sx && bit != State::S1) {
+ always_1[lane] = false;
+ }
+ }
}
-
- if (mem.wr_ports.empty() && mem.inits.empty()) {
- // The whole memory array will contain
- // only State::Sx, but the embedded read
- // registers could have reset or init values.
- // They will probably be optimized away by
- // opt_dff later.
- for (int i = 0; i < GetSize(mem.rd_ports); i++) {
- mem.extract_rdff(i, &initvals);
- auto &port = mem.rd_ports[i];
- module->connect(port.data, Const(State::Sx, GetSize(port.data)));
+ std::vector<int> swizzle;
+ for (int i = 0; i < mem.width; i++) {
+ if (!always_0[i] && !always_1[i]) {
+ swizzle.push_back(i);
+ continue;
}
+ State bit;
+ if (!always_0[i]) {
+ log("%s.%s: removing const-1 lane %d\n", log_id(module->name), log_id(mem.memid), i);
+ bit = State::S1;
+ } else if (!always_1[i]) {
+ log("%s.%s: removing const-0 lane %d\n", log_id(module->name), log_id(mem.memid), i);
+ bit = State::S0;
+ } else {
+ log("%s.%s: removing const-x lane %d\n", log_id(module->name), log_id(mem.memid), i);
+ bit = State::Sx;
+ }
+ // Reconnect read port data.
+ for (auto &port: mem.rd_ports) {
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++) {
+ int bidx = sub * mem.width + i;
+ if (!port.clk_enable) {
+ module->connect(port.data[bidx], bit);
+ } else {
+ // The FF will most likely be redundant, but it's up to opt_dff to deal with this.
+ FfData ff(module, &initvals, NEW_ID);
+ ff.width = 1;
+ ff.has_clk = true;
+ ff.sig_clk = port.clk;
+ ff.pol_clk = port.clk_polarity;
+ if (port.en != State::S1) {
+ ff.has_ce = true;
+ ff.pol_ce = true;
+ ff.sig_ce = port.en;
+ }
+ if (port.arst != State::S0) {
+ ff.has_arst = true;
+ ff.pol_arst = true;
+ ff.sig_arst = port.arst;
+ ff.val_arst = port.arst_value[bidx];
+ }
+ if (port.srst != State::S0) {
+ ff.has_srst = true;
+ ff.pol_srst = true;
+ ff.sig_srst = port.srst;
+ ff.val_srst = port.srst_value[bidx];
+ }
+ ff.sig_d = bit;
+ ff.sig_q = port.data[bidx];
+ ff.val_init = port.init_value[bidx];
+ ff.emit();
+ }
+ }
+ }
+ }
+ if (GetSize(swizzle) == 0) {
mem.remove();
total_count++;
+ continue;
+ }
+ if (GetSize(swizzle) != mem.width) {
+ for (auto &port: mem.wr_ports) {
+ SigSpec new_data;
+ SigSpec new_en;
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++) {
+ for (auto i: swizzle) {
+ new_data.append(port.data[sub * mem.width + i]);
+ new_en.append(port.en[sub * mem.width + i]);
+ }
+ }
+ port.data = new_data;
+ port.en = new_en;
+ }
+ for (auto &port: mem.rd_ports) {
+ SigSpec new_data;
+ Const new_init;
+ Const new_arst;
+ Const new_srst;
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++) {
+ for (auto i: swizzle) {
+ int bidx = sub * mem.width + i;
+ new_data.append(port.data[bidx]);
+ new_init.bits.push_back(port.init_value.bits[bidx]);
+ new_arst.bits.push_back(port.arst_value.bits[bidx]);
+ new_srst.bits.push_back(port.srst_value.bits[bidx]);
+ }
+ }
+ port.data = new_data;
+ port.init_value = new_init;
+ port.arst_value = new_arst;
+ port.srst_value = new_srst;
+ }
+ for (auto &init: mem.inits) {
+ Const new_data;
+ Const new_en;
+ for (int s = 0; s < GetSize(init.data); s += mem.width) {
+ for (auto i: swizzle) {
+ new_data.bits.push_back(init.data.bits[s + i]);
+ }
+ }
+ for (auto i: swizzle) {
+ new_en.bits.push_back(init.en.bits[i]);
+ }
+ init.data = new_data;
+ init.en = new_en;
+ }
+ mem.width = GetSize(swizzle);
+ changed = true;
+ total_count++;
+ }
+ if (changed) {
+ mem.emit();
}
}
}
diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc
index 115eb97a9..e9d98cd43 100644
--- a/passes/opt/opt_merge.cc
+++ b/passes/opt/opt_merge.cc
@@ -219,7 +219,15 @@ struct OptMergeWorker
return conn1 == conn2;
}
- OptMergeWorker(RTLIL::Design *design, RTLIL::Module *module, bool mode_nomux, bool mode_share_all) :
+ bool has_dont_care_initval(const RTLIL::Cell *cell)
+ {
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type))
+ return false;
+
+ return !initvals(cell->getPort(ID::Q)).is_fully_def();
+ }
+
+ OptMergeWorker(RTLIL::Design *design, RTLIL::Module *module, bool mode_nomux, bool mode_share_all, bool mode_keepdc) :
design(design), module(module), assign_map(module), mode_share_all(mode_share_all)
{
total_count = 0;
@@ -253,6 +261,8 @@ struct OptMergeWorker
for (auto &it : module->cells_) {
if (!design->selected(module, it.second))
continue;
+ if (mode_keepdc && has_dont_care_initval(it.second))
+ continue;
if (ct.cell_known(it.second->type) || (mode_share_all && it.second->known()))
cells.push_back(it.second);
}
@@ -319,6 +329,9 @@ struct OptMergePass : public Pass {
log(" -share_all\n");
log(" Operate on all cell types, not just built-in types.\n");
log("\n");
+ log(" -keepdc\n");
+ log(" Do not merge flipflops with don't-care bits in their initial value.\n");
+ log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
@@ -326,6 +339,7 @@ struct OptMergePass : public Pass {
bool mode_nomux = false;
bool mode_share_all = false;
+ bool mode_keepdc = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
@@ -338,13 +352,17 @@ struct OptMergePass : public Pass {
mode_share_all = true;
continue;
}
+ if (arg == "-keepdc") {
+ mode_keepdc = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
int total_count = 0;
for (auto module : design->selected_modules()) {
- OptMergeWorker worker(design, module, mode_nomux, mode_share_all);
+ OptMergeWorker worker(design, module, mode_nomux, mode_share_all, mode_keepdc);
total_count += worker.total_count;
}
diff --git a/passes/opt/opt_reduce.cc b/passes/opt/opt_reduce.cc
index b558f547e..1a7c93fbd 100644
--- a/passes/opt/opt_reduce.cc
+++ b/passes/opt/opt_reduce.cc
@@ -100,7 +100,7 @@ struct OptReduceWorker
return;
}
- void opt_mux(RTLIL::Cell *cell)
+ void opt_pmux(RTLIL::Cell *cell)
{
RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A));
RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B));
@@ -141,20 +141,20 @@ struct OptReduceWorker
handled_sig.insert(this_b);
}
- if (new_sig_s.size() != sig_s.size()) {
- log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s));
- did_something = true;
- total_count++;
- }
-
if (new_sig_s.size() == 0)
{
- module->connect(RTLIL::SigSig(cell->getPort(ID::Y), cell->getPort(ID::A)));
+ module->connect(cell->getPort(ID::Y), cell->getPort(ID::A));
assign_map.add(cell->getPort(ID::Y), cell->getPort(ID::A));
module->remove(cell);
+ did_something = true;
+ total_count++;
+ return;
}
- else
- {
+
+ if (new_sig_s.size() != sig_s.size() || (new_sig_s.size() == 1 && cell->type == ID($pmux))) {
+ log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s));
+ did_something = true;
+ total_count++;
cell->setPort(ID::B, new_sig_b);
cell->setPort(ID::S, new_sig_s);
if (new_sig_s.size() > 1) {
@@ -166,81 +166,347 @@ struct OptReduceWorker
}
}
- void opt_mux_bits(RTLIL::Cell *cell)
+ void opt_bmux(RTLIL::Cell *cell)
{
- std::vector<RTLIL::SigBit> sig_a = assign_map(cell->getPort(ID::A)).to_sigbit_vector();
- std::vector<RTLIL::SigBit> sig_b = assign_map(cell->getPort(ID::B)).to_sigbit_vector();
- std::vector<RTLIL::SigBit> sig_y = assign_map(cell->getPort(ID::Y)).to_sigbit_vector();
+ RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A));
+ RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S));
+ int width = cell->getParam(ID::WIDTH).as_int();
+
+ RTLIL::SigSpec new_sig_a, new_sig_s;
+ dict<RTLIL::SigBit, int> handled_bits;
+
+ // 0 and up: index of new_sig_s bit
+ // -1: const 0
+ // -2: const 1
+ std::vector<int> swizzle;
+
+ for (int i = 0; i < sig_s.size(); i++)
+ {
+ SigBit bit = sig_s[i];
+ if (bit == State::S0) {
+ swizzle.push_back(-1);
+ } else if (bit == State::S1) {
+ swizzle.push_back(-2);
+ } else {
+ auto it = handled_bits.find(bit);
+ if (it == handled_bits.end()) {
+ int new_idx = GetSize(new_sig_s);
+ new_sig_s.append(bit);
+ handled_bits[bit] = new_idx;
+ swizzle.push_back(new_idx);
+ } else {
+ swizzle.push_back(it->second);
+ }
+ }
+ }
+
+ for (int i = 0; i < (1 << GetSize(new_sig_s)); i++) {
+ int idx = 0;
+ for (int j = 0; j < GetSize(sig_s); j++) {
+ if (swizzle[j] == -1) {
+ // const 0.
+ } else if (swizzle[j] == -2) {
+ // const 1.
+ idx |= 1 << j;
+ } else {
+ if (i & 1 << swizzle[j])
+ idx |= 1 << j;
+ }
+ }
+ new_sig_a.append(sig_a.extract(idx * width, width));
+ }
+
+ if (new_sig_s.size() == 0)
+ {
+ module->connect(cell->getPort(ID::Y), new_sig_a);
+ assign_map.add(cell->getPort(ID::Y), new_sig_a);
+ module->remove(cell);
+ did_something = true;
+ total_count++;
+ return;
+ }
+
+ if (new_sig_s.size() == 1)
+ {
+ cell->type = ID($mux);
+ cell->setPort(ID::A, new_sig_a.extract(0, width));
+ cell->setPort(ID::B, new_sig_a.extract(width, width));
+ cell->setPort(ID::S, new_sig_s);
+ cell->parameters.erase(ID::S_WIDTH);
+ did_something = true;
+ total_count++;
+ return;
+ }
+
+ if (new_sig_s.size() != sig_s.size()) {
+ log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s));
+ did_something = true;
+ total_count++;
+ cell->setPort(ID::A, new_sig_a);
+ cell->setPort(ID::S, new_sig_s);
+ cell->parameters[ID::S_WIDTH] = RTLIL::Const(new_sig_s.size());
+ }
+ }
+
+ void opt_demux(RTLIL::Cell *cell)
+ {
+ RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y));
+ RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S));
+ int width = cell->getParam(ID::WIDTH).as_int();
+
+ RTLIL::SigSpec new_sig_y, new_sig_s;
+ dict<RTLIL::SigBit, int> handled_bits;
+
+ // 0 and up: index of new_sig_s bit
+ // -1: const 0
+ // -2: const 1
+ std::vector<int> swizzle;
+
+ for (int i = 0; i < sig_s.size(); i++)
+ {
+ SigBit bit = sig_s[i];
+ if (bit == State::S0) {
+ swizzle.push_back(-1);
+ } else if (bit == State::S1) {
+ swizzle.push_back(-2);
+ } else {
+ auto it = handled_bits.find(bit);
+ if (it == handled_bits.end()) {
+ int new_idx = GetSize(new_sig_s);
+ new_sig_s.append(bit);
+ handled_bits[bit] = new_idx;
+ swizzle.push_back(new_idx);
+ } else {
+ swizzle.push_back(it->second);
+ }
+ }
+ }
+
+ pool<int> nonzero_idx;
+
+ for (int i = 0; i < (1 << GetSize(new_sig_s)); i++) {
+ int idx = 0;
+ for (int j = 0; j < GetSize(sig_s); j++) {
+ if (swizzle[j] == -1) {
+ // const 0.
+ } else if (swizzle[j] == -2) {
+ // const 1.
+ idx |= 1 << j;
+ } else {
+ if (i & 1 << swizzle[j])
+ idx |= 1 << j;
+ }
+ }
+ log_assert(!nonzero_idx.count(idx));
+ nonzero_idx.insert(idx);
+ new_sig_y.append(sig_y.extract(idx * width, width));
+ }
+
+ if (new_sig_s.size() == sig_s.size() && sig_s.size() > 0)
+ return;
+
+ log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s));
+ did_something = true;
+ total_count++;
+
+ for (int i = 0; i < (1 << GetSize(sig_s)); i++) {
+ if (!nonzero_idx.count(i)) {
+ SigSpec slice = sig_y.extract(i * width, width);
+ module->connect(slice, Const(State::S0, width));
+ assign_map.add(slice, Const(State::S0, width));
+ }
+ }
+
+ if (new_sig_s.size() == 0)
+ {
+ module->connect(new_sig_y, cell->getPort(ID::A));
+ assign_map.add(new_sig_y, cell->getPort(ID::A));
+ module->remove(cell);
+ }
+ else
+ {
+ cell->setPort(ID::S, new_sig_s);
+ cell->setPort(ID::Y, new_sig_y);
+ cell->parameters[ID::S_WIDTH] = RTLIL::Const(new_sig_s.size());
+ }
+ }
+
+ bool opt_mux_bits(RTLIL::Cell *cell)
+ {
+ SigSpec sig_a = assign_map(cell->getPort(ID::A));
+ SigSpec sig_b;
+ SigSpec sig_y = assign_map(cell->getPort(ID::Y));
+ int width = GetSize(sig_y);
+
+ if (cell->type != ID($bmux))
+ sig_b = assign_map(cell->getPort(ID::B));
- std::vector<RTLIL::SigBit> new_sig_y;
RTLIL::SigSig old_sig_conn;
- std::vector<std::vector<RTLIL::SigBit>> consolidated_in_tuples;
- std::map<std::vector<RTLIL::SigBit>, RTLIL::SigBit> consolidated_in_tuples_map;
+ dict<SigSpec, SigBit> consolidated_in_tuples;
+ std::vector<int> swizzle;
- for (int i = 0; i < int(sig_y.size()); i++)
+ for (int i = 0; i < width; i++)
{
- std::vector<RTLIL::SigBit> in_tuple;
+ SigSpec in_tuple;
bool all_tuple_bits_same = true;
- in_tuple.push_back(sig_a.at(i));
- for (int j = i; j < int(sig_b.size()); j += int(sig_a.size())) {
- if (sig_b.at(j) != sig_a.at(i))
+ in_tuple.append(sig_a[i]);
+ for (int j = i; j < GetSize(sig_a); j += width) {
+ in_tuple.append(sig_a[j]);
+ if (sig_a[j] != in_tuple[0])
+ all_tuple_bits_same = false;
+ }
+ for (int j = i; j < GetSize(sig_b); j += width) {
+ in_tuple.append(sig_b[j]);
+ if (sig_b[j] != in_tuple[0])
all_tuple_bits_same = false;
- in_tuple.push_back(sig_b.at(j));
}
if (all_tuple_bits_same)
{
- old_sig_conn.first.append(sig_y.at(i));
- old_sig_conn.second.append(sig_a.at(i));
+ old_sig_conn.first.append(sig_y[i]);
+ old_sig_conn.second.append(sig_a[i]);
+ continue;
}
- else if (consolidated_in_tuples_map.count(in_tuple))
+
+ auto it = consolidated_in_tuples.find(in_tuple);
+ if (it == consolidated_in_tuples.end())
{
- old_sig_conn.first.append(sig_y.at(i));
- old_sig_conn.second.append(consolidated_in_tuples_map.at(in_tuple));
+ consolidated_in_tuples[in_tuple] = sig_y[i];
+ swizzle.push_back(i);
}
else
{
- consolidated_in_tuples_map[in_tuple] = sig_y.at(i);
- consolidated_in_tuples.push_back(in_tuple);
- new_sig_y.push_back(sig_y.at(i));
+ old_sig_conn.first.append(sig_y[i]);
+ old_sig_conn.second.append(it->second);
}
}
- if (new_sig_y.size() != sig_y.size())
+ if (GetSize(swizzle) != width)
{
log(" Consolidated identical input bits for %s cell %s:\n", cell->type.c_str(), cell->name.c_str());
- log(" Old ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
- log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y)));
-
- cell->setPort(ID::A, RTLIL::SigSpec());
- for (auto &in_tuple : consolidated_in_tuples) {
- RTLIL::SigSpec new_a = cell->getPort(ID::A);
- new_a.append(in_tuple.at(0));
- cell->setPort(ID::A, new_a);
+ if (cell->type != ID($bmux)) {
+ log(" Old ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y)));
+ } else {
+ log(" Old ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::Y)));
}
- cell->setPort(ID::B, RTLIL::SigSpec());
- for (int i = 1; i <= cell->getPort(ID::S).size(); i++)
- for (auto &in_tuple : consolidated_in_tuples) {
- RTLIL::SigSpec new_b = cell->getPort(ID::B);
- new_b.append(in_tuple.at(i));
- cell->setPort(ID::B, new_b);
+ if (swizzle.empty()) {
+ module->remove(cell);
+ } else {
+ SigSpec new_sig_a;
+ for (int i = 0; i < GetSize(sig_a); i += width)
+ for (int j: swizzle)
+ new_sig_a.append(sig_a[i+j]);
+ cell->setPort(ID::A, new_sig_a);
+
+ if (cell->type != ID($bmux)) {
+ SigSpec new_sig_b;
+ for (int i = 0; i < GetSize(sig_b); i += width)
+ for (int j: swizzle)
+ new_sig_b.append(sig_b[i+j]);
+ cell->setPort(ID::B, new_sig_b);
}
- cell->parameters[ID::WIDTH] = RTLIL::Const(new_sig_y.size());
- cell->setPort(ID::Y, new_sig_y);
+ SigSpec new_sig_y;
+ for (int j: swizzle)
+ new_sig_y.append(sig_y[j]);
+ cell->setPort(ID::Y, new_sig_y);
+
+ cell->parameters[ID::WIDTH] = RTLIL::Const(GetSize(swizzle));
+
+ if (cell->type != ID($bmux)) {
+ log(" New ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y)));
+ } else {
+ log(" New ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::Y)));
+ }
+ }
- log(" New ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
- log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y)));
log(" New connections: %s = %s\n", log_signal(old_sig_conn.first), log_signal(old_sig_conn.second));
+ module->connect(old_sig_conn);
+
+ did_something = true;
+ total_count++;
+ }
+ return swizzle.empty();
+ }
+
+ bool opt_demux_bits(RTLIL::Cell *cell) {
+ SigSpec sig_a = assign_map(cell->getPort(ID::A));
+ SigSpec sig_y = assign_map(cell->getPort(ID::Y));
+ int width = GetSize(sig_a);
+
+ RTLIL::SigSig old_sig_conn;
+
+ dict<SigBit, int> handled_bits;
+ std::vector<int> swizzle;
+
+ for (int i = 0; i < width; i++)
+ {
+ if (sig_a[i] == State::S0)
+ {
+ for (int j = i; j < GetSize(sig_y); j += width)
+ {
+ old_sig_conn.first.append(sig_y[j]);
+ old_sig_conn.second.append(State::S0);
+ }
+ continue;
+ }
+ auto it = handled_bits.find(sig_a[i]);
+ if (it == handled_bits.end())
+ {
+ handled_bits[sig_a[i]] = i;
+ swizzle.push_back(i);
+ }
+ else
+ {
+ for (int j = 0; j < GetSize(sig_y); j += width)
+ {
+ old_sig_conn.first.append(sig_y[i+j]);
+ old_sig_conn.second.append(sig_y[it->second+j]);
+ }
+ }
+ }
+
+ if (GetSize(swizzle) != width)
+ {
+ log(" Consolidated identical input bits for %s cell %s:\n", cell->type.c_str(), cell->name.c_str());
+ log(" Old ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::Y)));
+
+ if (swizzle.empty()) {
+ module->remove(cell);
+ } else {
+ SigSpec new_sig_a;
+ for (int j: swizzle)
+ new_sig_a.append(sig_a[j]);
+ cell->setPort(ID::A, new_sig_a);
+
+ SigSpec new_sig_y;
+ for (int i = 0; i < GetSize(sig_y); i += width)
+ for (int j: swizzle)
+ new_sig_y.append(sig_y[i+j]);
+ cell->setPort(ID::Y, new_sig_y);
+
+ cell->parameters[ID::WIDTH] = RTLIL::Const(GetSize(swizzle));
+
+ log(" New ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)),
+ log_signal(cell->getPort(ID::Y)));
+ }
+
+ log(" New connections: %s = %s\n", log_signal(old_sig_conn.first), log_signal(old_sig_conn.second));
module->connect(old_sig_conn);
did_something = true;
total_count++;
}
+ return swizzle.empty();
}
OptReduceWorker(RTLIL::Design *design, RTLIL::Module *module, bool do_fine) :
@@ -309,20 +575,31 @@ struct OptReduceWorker
// merge identical inputs on $mux and $pmux cells
- std::vector<RTLIL::Cell*> cells;
-
- for (auto &it : module->cells_)
- if ((it.second->type == ID($mux) || it.second->type == ID($pmux)) && design->selected(module, it.second))
- cells.push_back(it.second);
-
- for (auto cell : cells)
+ for (auto cell : module->selected_cells())
{
+ if (!cell->type.in(ID($mux), ID($pmux), ID($bmux), ID($demux)))
+ continue;
+
// this optimization is to aggressive for most coarse-grain applications.
// but we always want it for multiplexers driving write enable ports.
- if (do_fine || mem_wren_sigs.check_any(assign_map(cell->getPort(ID::Y))))
- opt_mux_bits(cell);
+ if (do_fine || mem_wren_sigs.check_any(assign_map(cell->getPort(ID::Y)))) {
+ if (cell->type == ID($demux)) {
+ if (opt_demux_bits(cell))
+ continue;
+ } else {
+ if (opt_mux_bits(cell))
+ continue;
+ }
+ }
+
+ if (cell->type.in(ID($mux), ID($pmux)))
+ opt_pmux(cell);
+
+ if (cell->type == ID($bmux))
+ opt_bmux(cell);
- opt_mux(cell);
+ if (cell->type == ID($demux))
+ opt_demux(cell);
}
}
diff --git a/passes/opt/wreduce.cc b/passes/opt/wreduce.cc
index aaad28ef0..08ab6de6f 100644
--- a/passes/opt/wreduce.cc
+++ b/passes/opt/wreduce.cc
@@ -30,6 +30,7 @@ struct WreduceConfig
{
pool<IdString> supported_cell_types;
bool keepdc = false;
+ bool mux_undef = false;
WreduceConfig()
{
@@ -83,7 +84,7 @@ struct WreduceWorker
SigBit ref = sig_a[i];
for (int k = 0; k < GetSize(sig_s); k++) {
- if ((config->keepdc || (ref != State::Sx && sig_b[k*GetSize(sig_a) + i] != State::Sx)) && ref != sig_b[k*GetSize(sig_a) + i])
+ if ((config->keepdc || !config->mux_undef || (ref != State::Sx && sig_b[k*GetSize(sig_a) + i] != State::Sx)) && ref != sig_b[k*GetSize(sig_a) + i])
goto no_match_ab;
if (sig_b[k*GetSize(sig_a) + i] != State::Sx)
ref = sig_b[k*GetSize(sig_a) + i];
@@ -479,6 +480,9 @@ struct WreducePass : public Pass {
log(" Do not change the width of memory address ports. Use this options in\n");
log(" flows that use the 'memory_memx' pass.\n");
log("\n");
+ log(" -mux_undef\n");
+ log(" remove 'undef' inputs from $mux, $pmux and $_MUX_ cells\n");
+ log("\n");
log(" -keepdc\n");
log(" Do not optimize explicit don't-care values.\n");
log("\n");
@@ -500,6 +504,10 @@ struct WreducePass : public Pass {
config.keepdc = true;
continue;
}
+ if (args[argidx] == "-mux_undef") {
+ config.mux_undef = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg
index 7a01cbd51..4de479122 100644
--- a/passes/pmgen/ice40_dsp.pmg
+++ b/passes/pmgen/ice40_dsp.pmg
@@ -28,9 +28,8 @@ code sigA sigB sigH
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;
+ // Do not remove sign bit
+ ++i;
return sig.extract(0, i);
};
sigA = unextend(port(mul, \A));
diff --git a/passes/proc/Makefile.inc b/passes/proc/Makefile.inc
index 50244bf33..21e169a34 100644
--- a/passes/proc/Makefile.inc
+++ b/passes/proc/Makefile.inc
@@ -5,6 +5,7 @@ OBJS += passes/proc/proc_clean.o
OBJS += passes/proc/proc_rmdead.o
OBJS += passes/proc/proc_init.o
OBJS += passes/proc/proc_arst.o
+OBJS += passes/proc/proc_rom.o
OBJS += passes/proc/proc_mux.o
OBJS += passes/proc/proc_dlatch.o
OBJS += passes/proc/proc_dff.o
diff --git a/passes/proc/proc.cc b/passes/proc/proc.cc
index d7aac57b6..c18651d5e 100644
--- a/passes/proc/proc.cc
+++ b/passes/proc/proc.cc
@@ -40,6 +40,7 @@ struct ProcPass : public Pass {
log(" proc_prune\n");
log(" proc_init\n");
log(" proc_arst\n");
+ log(" proc_rom\n");
log(" proc_mux\n");
log(" proc_dlatch\n");
log(" proc_dff\n");
@@ -55,6 +56,9 @@ struct ProcPass : public Pass {
log(" -nomux\n");
log(" Will omit the proc_mux pass.\n");
log("\n");
+ log(" -norom\n");
+ log(" Will omit the proc_rom pass.\n");
+ log("\n");
log(" -global_arst [!]<netname>\n");
log(" This option is passed through to proc_arst.\n");
log("\n");
@@ -72,6 +76,7 @@ struct ProcPass : public Pass {
bool ifxmode = false;
bool nomux = false;
bool noopt = false;
+ bool norom = false;
log_header(design, "Executing PROC pass (convert processes to netlists).\n");
log_push();
@@ -95,6 +100,10 @@ struct ProcPass : public Pass {
noopt = true;
continue;
}
+ if (args[argidx] == "-norom") {
+ norom = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
@@ -108,6 +117,8 @@ struct ProcPass : public Pass {
Pass::call(design, "proc_arst");
else
Pass::call(design, "proc_arst -global_arst " + global_arst);
+ if (!norom)
+ Pass::call(design, "proc_rom");
if (!nomux)
Pass::call(design, ifxmode ? "proc_mux -ifx" : "proc_mux");
Pass::call(design, "proc_dlatch");
diff --git a/passes/proc/proc_rom.cc b/passes/proc/proc_rom.cc
new file mode 100644
index 000000000..b83466ce7
--- /dev/null
+++ b/passes/proc/proc_rom.cc
@@ -0,0 +1,252 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2022 Marcelina Koƛcielnicka <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/register.h"
+#include "kernel/sigtools.h"
+#include "kernel/log.h"
+#include "kernel/mem.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct RomWorker
+{
+ RTLIL::Module *module;
+ SigMap sigmap;
+
+ int count = 0;
+
+ RomWorker(RTLIL::Module *mod) : module(mod), sigmap(mod) {}
+
+ void do_switch(RTLIL::SwitchRule *sw)
+ {
+ for (auto cs : sw->cases) {
+ do_case(cs);
+ }
+
+ if (sw->cases.empty()) {
+ log_debug("rejecting switch: no cases\n");
+ return;
+ }
+
+ // A switch can be converted into ROM when:
+ //
+ // 1. No case contains a nested switch
+ // 2. All cases have the same set of assigned signals
+ // 3. All right-hand values in cases are constants
+ // 4. All compare values used in cases are fully-defined constants
+ // 5. The cases must cover all possible values (possibly by using default case)
+
+ SigSpec lhs;
+ dict<SigBit, int> lhs_lookup;
+ for (auto &it: sw->cases[0]->actions) {
+ for (auto bit: it.first) {
+ if (!lhs_lookup.count(bit)) {
+ lhs_lookup[bit] = GetSize(lhs);
+ lhs.append(bit);
+ }
+ }
+ }
+
+ int swsigbits = 0;
+ for (int i = 0; i < GetSize(sw->signal); i++)
+ if (sw->signal[i] != State::S0)
+ swsigbits = i + 1;
+
+ dict<int, Const> vals;
+ Const default_val;
+ bool got_default = false;
+ int maxaddr = 0;
+ for (auto cs : sw->cases) {
+ if (!cs->switches.empty()) {
+ log_debug("rejecting switch: has nested switches\n");
+ return;
+ }
+ Const val = Const(State::Sm, GetSize(lhs));
+ for (auto &it: cs->actions) {
+ if (!it.second.is_fully_const()) {
+ log_debug("rejecting switch: rhs not const\n");
+ return;
+ }
+ for (int i = 0; i < GetSize(it.first); i++) {
+ auto it2 = lhs_lookup.find(it.first[i]);
+ if (it2 == lhs_lookup.end()) {
+ log_debug("rejecting switch: lhs not uniform\n");
+ return;
+ }
+ val[it2->second] = it.second[i].data;
+ }
+ }
+ for (auto bit: val.bits) {
+ if (bit == State::Sm) {
+ log_debug("rejecting switch: lhs not uniform\n");
+ return;
+ }
+ }
+
+ for (auto &addr: cs->compare) {
+ if (!addr.is_fully_def()) {
+ log_debug("rejecting switch: case value has undef bits\n");
+ return;
+ }
+ Const c = addr.as_const();
+ while (GetSize(c) && c.bits.back() == State::S0)
+ c.bits.pop_back();
+ if (GetSize(c) > swsigbits)
+ continue;
+ if (GetSize(c) > 30) {
+ log_debug("rejecting switch: address too large\n");
+ return;
+ }
+ int a = c.as_int();
+ if (vals.count(a))
+ continue;
+ vals[a] = val;
+ if (a > maxaddr)
+ maxaddr = a;
+ }
+ if (cs->compare.empty()) {
+ default_val = val;
+ got_default = true;
+ break;
+ }
+ }
+ int abits = ceil_log2(maxaddr + 1);
+ if (!got_default && (swsigbits > 30 || GetSize(vals) != (1 << swsigbits))) {
+ log_debug("rejecting switch: not all values are covered\n");
+ return;
+ }
+
+ // TODO: better density heuristic?
+ if (GetSize(vals) < 8) {
+ log_debug("rejecting switch: not enough values\n");
+ return;
+ }
+ if ((1 << abits) / GetSize(vals) > 4) {
+ log_debug("rejecting switch: not enough density\n");
+ return;
+ }
+
+ // Ok, let's do it.
+ SigSpec rdata = module->addWire(NEW_ID, GetSize(lhs));
+ Mem mem(module, NEW_ID, GetSize(lhs), 0, 1 << abits);
+ mem.attributes = sw->attributes;
+
+ Const init_data;
+ for (int i = 0; i < mem.size; i++) {
+ auto it = vals.find(i);
+ if (it == vals.end()) {
+ log_assert(got_default);
+ for (auto bit: default_val.bits)
+ init_data.bits.push_back(bit);
+ } else {
+ for (auto bit: it->second.bits)
+ init_data.bits.push_back(bit);
+ }
+ }
+
+ MemInit init;
+ init.addr = 0;
+ init.data = init_data;
+ init.en = Const(State::S1, GetSize(lhs));
+ mem.inits.push_back(std::move(init));
+
+ MemRd rd;
+ rd.addr = sw->signal.extract(0, abits);
+ rd.data = rdata;
+ rd.init_value = Const(State::Sx, GetSize(lhs));
+ rd.arst_value = Const(State::Sx, GetSize(lhs));
+ rd.srst_value = Const(State::Sx, GetSize(lhs));
+ mem.rd_ports.push_back(std::move(rd));
+
+ mem.emit();
+ for (auto cs: sw->cases)
+ delete cs;
+ sw->cases.clear();
+ sw->signal = sw->signal.extract(0, swsigbits);
+ if (abits == GetSize(sw->signal)) {
+ sw->signal = SigSpec();
+ RTLIL::CaseRule *cs = new RTLIL::CaseRule;
+ cs->actions.push_back(SigSig(lhs, rdata));
+ sw->cases.push_back(cs);
+ } else {
+ sw->signal = sw->signal.extract_end(abits);
+ RTLIL::CaseRule *cs = new RTLIL::CaseRule;
+ cs->compare.push_back(Const(State::S0, GetSize(sw->signal)));
+ cs->actions.push_back(SigSig(lhs, rdata));
+ sw->cases.push_back(cs);
+ RTLIL::CaseRule *cs2 = new RTLIL::CaseRule;
+ cs2->actions.push_back(SigSig(lhs, default_val));
+ sw->cases.push_back(cs2);
+ }
+
+ count += 1;
+ }
+
+ void do_case(RTLIL::CaseRule *cs)
+ {
+ for (auto sw: cs->switches) {
+ do_switch(sw);
+ }
+ }
+
+ void do_process(RTLIL::Process *pr)
+ {
+ do_case(&pr->root_case);
+ }
+};
+
+struct ProcRomPass : public Pass {
+ ProcRomPass() : Pass("proc_rom", "convert switches to ROMs") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" proc_rom [selection]\n");
+ log("\n");
+ log("This pass converts switches into read-only memories when appropriate.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ int total_count = 0;
+ log_header(design, "Executing PROC_ROM pass (convert switches to ROMs).\n");
+
+ extra_args(args, 1, design);
+
+ for (auto mod : design->modules()) {
+ if (!design->selected(mod))
+ continue;
+ RomWorker worker(mod);
+ for (auto &proc_it : mod->processes) {
+ if (!design->selected(mod, proc_it.second))
+ continue;
+ worker.do_process(proc_it.second);
+ }
+ total_count += worker.count;
+ }
+
+ log("Converted %d switch%s.\n",
+ total_count, total_count == 1 ? "" : "es");
+ }
+} ProcRomPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/sat/Makefile.inc b/passes/sat/Makefile.inc
index 7118c1563..da6d49433 100644
--- a/passes/sat/Makefile.inc
+++ b/passes/sat/Makefile.inc
@@ -2,7 +2,9 @@
OBJS += passes/sat/sat.o
OBJS += passes/sat/freduce.o
OBJS += passes/sat/eval.o
+ifeq ($(ENABLE_ZLIB),1)
OBJS += passes/sat/sim.o
+endif
OBJS += passes/sat/miter.o
OBJS += passes/sat/expose.o
OBJS += passes/sat/assertpmux.o
diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc
index a292941c8..b1b0567a0 100644
--- a/passes/sat/clk2fflogic.cc
+++ b/passes/sat/clk2fflogic.cc
@@ -39,8 +39,14 @@ struct Clk2fflogicPass : public Pass {
log("multiple clocks.\n");
log("\n");
}
- SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity) {
- Wire *past_sig = module->addWire(NEW_ID, GetSize(sig));
+ SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, bool is_fine, IdString past_sig_id) {
+ if (!is_fine)
+ return wrap_async_control(module, sig, polarity, past_sig_id);
+ return wrap_async_control_gate(module, sig, polarity, past_sig_id);
+ }
+ SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) {
+ Wire *past_sig = module->addWire(past_sig_id, GetSize(sig));
+ past_sig->attributes[ID::init] = RTLIL::Const(polarity ? State::S0 : State::S1, GetSize(sig));
module->addFf(NEW_ID, sig, past_sig);
if (polarity)
sig = module->Or(NEW_ID, sig, past_sig);
@@ -51,8 +57,9 @@ struct Clk2fflogicPass : public Pass {
else
return module->Not(NEW_ID, sig);
}
- SigSpec wrap_async_control_gate(Module *module, SigSpec sig, bool polarity) {
- Wire *past_sig = module->addWire(NEW_ID);
+ SigSpec wrap_async_control_gate(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) {
+ Wire *past_sig = module->addWire(past_sig_id);
+ past_sig->attributes[ID::init] = polarity ? State::S0 : State::S1;
module->addFfGate(NEW_ID, sig, past_sig);
if (polarity)
sig = module->OrGate(NEW_ID, sig, past_sig);
@@ -105,7 +112,7 @@ struct Clk2fflogicPass : public Pass {
i, log_id(module), log_id(mem.memid), log_signal(port.clk),
log_signal(port.addr), log_signal(port.data));
- Wire *past_clk = module->addWire(NEW_ID);
+ Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#past_clk#%s", log_id(mem.memid), i, log_signal(port.clk))));
past_clk->attributes[ID::init] = port.clk_polarity ? State::S1 : State::S0;
module->addFf(NEW_ID, port.clk, past_clk);
@@ -121,13 +128,13 @@ struct Clk2fflogicPass : public Pass {
SigSpec clock_edge = module->Eqx(NEW_ID, {port.clk, SigSpec(past_clk)}, clock_edge_pattern);
- SigSpec en_q = module->addWire(NEW_ID, GetSize(port.en));
+ SigSpec en_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#en_q", log_id(mem.memid), i)), GetSize(port.en));
module->addFf(NEW_ID, port.en, en_q);
- SigSpec addr_q = module->addWire(NEW_ID, GetSize(port.addr));
+ SigSpec addr_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#addr_q", log_id(mem.memid), i)), GetSize(port.addr));
module->addFf(NEW_ID, port.addr, addr_q);
- SigSpec data_q = module->addWire(NEW_ID, GetSize(port.data));
+ SigSpec data_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#data_q", log_id(mem.memid), i)), GetSize(port.data));
module->addFf(NEW_ID, port.data, data_q);
port.clk = State::S0;
@@ -170,7 +177,14 @@ struct Clk2fflogicPass : public Pass {
ff.remove();
- Wire *past_q = module->addWire(NEW_ID, ff.width);
+ // Strip spaces from signal name, since Yosys IDs can't contain spaces
+ // Spaces only occur when we have a signal that's a slice of a larger bus,
+ // e.g. "\myreg [5:0]", so removing spaces shouldn't result in loss of uniqueness
+ std::string sig_q_str = log_signal(ff.sig_q);
+ sig_q_str.erase(std::remove(sig_q_str.begin(), sig_q_str.end(), ' '), sig_q_str.end());
+
+ Wire *past_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_q_wire", sig_q_str.c_str())), ff.width);
+
if (!ff.is_fine) {
module->addFf(NEW_ID, ff.sig_q, past_q);
} else {
@@ -182,7 +196,7 @@ struct Clk2fflogicPass : public Pass {
if (ff.has_clk) {
ff.unmap_ce_srst();
- Wire *past_clk = module->addWire(NEW_ID);
+ Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_clk#%s", sig_q_str.c_str(), log_signal(ff.sig_clk))));
initvals.set_init(past_clk, ff.pol_clk ? State::S1 : State::S0);
if (!ff.is_fine)
@@ -202,7 +216,7 @@ struct Clk2fflogicPass : public Pass {
SigSpec clock_edge = module->Eqx(NEW_ID, {ff.sig_clk, SigSpec(past_clk)}, clock_edge_pattern);
- Wire *past_d = module->addWire(NEW_ID, ff.width);
+ Wire *past_d = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_d_wire", sig_q_str.c_str())), ff.width);
if (!ff.is_fine)
module->addFf(NEW_ID, ff.sig_d, past_d);
else
@@ -220,7 +234,7 @@ struct Clk2fflogicPass : public Pass {
}
if (ff.has_aload) {
- SigSpec sig_aload = wrap_async_control(module, ff.sig_aload, ff.pol_aload);
+ SigSpec sig_aload = wrap_async_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine, NEW_ID);
if (!ff.is_fine)
qval = module->Mux(NEW_ID, qval, ff.sig_ad, sig_aload);
@@ -229,8 +243,8 @@ struct Clk2fflogicPass : public Pass {
}
if (ff.has_sr) {
- SigSpec setval = wrap_async_control(module, ff.sig_set, ff.pol_set);
- SigSpec clrval = wrap_async_control(module, ff.sig_clr, ff.pol_clr);
+ SigSpec setval = wrap_async_control(module, ff.sig_set, ff.pol_set, ff.is_fine, NEW_ID);
+ SigSpec clrval = wrap_async_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine, NEW_ID);
if (!ff.is_fine) {
clrval = module->Not(NEW_ID, clrval);
qval = module->Or(NEW_ID, qval, setval);
@@ -241,7 +255,8 @@ struct Clk2fflogicPass : public Pass {
module->addAndGate(NEW_ID, qval, clrval, ff.sig_q);
}
} else if (ff.has_arst) {
- SigSpec arst = wrap_async_control(module, ff.sig_arst, ff.pol_arst);
+ IdString id = NEW_ID_SUFFIX(stringf("%s#past_arst#%s", sig_q_str.c_str(), log_signal(ff.sig_arst)));
+ SigSpec arst = wrap_async_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine, id);
if (!ff.is_fine)
module->addMux(NEW_ID, qval, ff.val_arst, arst, ff.sig_q);
else
diff --git a/passes/sat/fmcombine.cc b/passes/sat/fmcombine.cc
index e15bdf6a8..220cf5c52 100644
--- a/passes/sat/fmcombine.cc
+++ b/passes/sat/fmcombine.cc
@@ -64,6 +64,9 @@ struct FmcombineWorker
c->parameters = cell->parameters;
c->attributes = cell->attributes;
+ if (cell->is_mem_cell())
+ c->parameters[ID::MEMID] = cell->parameters[ID::MEMID].decode_string() + suffix;
+
for (auto &conn : cell->connections())
c->setPort(conn.first, import_sig(conn.second, suffix));
diff --git a/passes/sat/qbfsat.cc b/passes/sat/qbfsat.cc
index 6db7d4b64..864d6f05d 100644
--- a/passes/sat/qbfsat.cc
+++ b/passes/sat/qbfsat.cc
@@ -251,7 +251,7 @@ QbfSolutionType call_qbf_solver(RTLIL::Module *mod, const QbfSolveOptions &opt,
QbfSolutionType qbf_solve(RTLIL::Module *mod, const QbfSolveOptions &opt) {
QbfSolutionType ret, best_soln;
- const std::string tempdir_name = make_temp_dir("/tmp/yosys-qbfsat-XXXXXX");
+ const std::string tempdir_name = make_temp_dir(get_base_tmpdir() + "/yosys-qbfsat-XXXXXX");
RTLIL::Module *module = mod;
RTLIL::Design *design = module->design;
std::string module_name = module->name.str();
diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc
index 4e158da62..d085fab2d 100644
--- a/passes/sat/sim.cc
+++ b/passes/sat/sim.cc
@@ -21,19 +21,77 @@
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/mem.h"
+#include "kernel/fstdata.h"
+#include "kernel/ff.h"
#include <ctime>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
+enum class SimulationMode {
+ sim,
+ cmp,
+ gold,
+ gate,
+};
+
+static const std::map<std::string, int> g_units =
+{
+ { "", -9 }, // default is ns
+ { "s", 0 },
+ { "ms", -3 },
+ { "us", -6 },
+ { "ns", -9 },
+ { "ps", -12 },
+ { "fs", -15 },
+ { "as", -18 },
+ { "zs", -21 },
+};
+
+static double stringToTime(std::string str)
+{
+ if (str=="END") return -1;
+
+ char *endptr;
+ long value = strtol(str.c_str(), &endptr, 10);
+
+ if (g_units.find(endptr)==g_units.end())
+ log_error("Cannot parse '%s', bad unit '%s'\n", str.c_str(), endptr);
+
+ if (value < 0)
+ log_error("Time value '%s' must be positive\n", str.c_str());
+
+ return value * pow(10.0, g_units.at(endptr));
+}
+
+struct SimWorker;
+struct OutputWriter
+{
+ OutputWriter(SimWorker *w) { worker = w;};
+ virtual ~OutputWriter() {};
+ virtual void write(std::map<int, bool> &use_signal) = 0;
+ SimWorker *worker;
+};
+
struct SimShared
{
bool debug = false;
+ bool verbose = true;
bool hide_internal = true;
bool writeback = false;
bool zinit = false;
int rstlen = 1;
+ FstData *fst = nullptr;
+ double start_time = 0;
+ double stop_time = -1;
+ SimulationMode sim_mode = SimulationMode::sim;
+ bool cycles_set = false;
+ std::vector<std::unique_ptr<OutputWriter>> outputfiles;
+ std::vector<std::pair<int,std::map<int,Const>>> output_data;
+ bool ignore_x = false;
+ bool date = false;
+ bool multiclock = false;
};
void zinit(State &v)
@@ -51,7 +109,8 @@ void zinit(Const &v)
struct SimInstance
{
SimShared *shared;
-
+
+ std::string scope;
Module *module;
Cell *instance;
@@ -70,8 +129,13 @@ struct SimInstance
struct ff_state_t
{
- State past_clock;
Const past_d;
+ Const past_ad;
+ State past_clk;
+ State past_ce;
+ State past_srst;
+
+ FfData data;
};
struct mem_state_t
@@ -91,10 +155,12 @@ struct SimInstance
std::vector<Mem> memories;
- dict<Wire*, pair<int, Const>> vcd_database;
+ dict<Wire*, pair<int, Const>> signal_database;
+ dict<Wire*, fstHandle> fst_handles;
+ dict<IdString, dict<int,fstHandle>> fst_memories;
- SimInstance(SimShared *shared, Module *module, Cell *instance = nullptr, SimInstance *parent = nullptr) :
- shared(shared), module(module), instance(instance), parent(parent), sigmap(module)
+ SimInstance(SimShared *shared, std::string scope, Module *module, Cell *instance = nullptr, SimInstance *parent = nullptr) :
+ shared(shared), scope(scope), module(module), instance(instance), parent(parent), sigmap(module)
{
log_assert(module);
@@ -116,6 +182,13 @@ struct SimInstance
}
}
+ if ((shared->fst) && !(shared->hide_internal && wire->name[0] == '$')) {
+ fstHandle id = shared->fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name));
+ if (id==0 && wire->name.isPublic())
+ log_warning("Unable to find wire %s in input file.\n", (scope + "." + RTLIL::unescape_id(wire->name)).c_str());
+ fst_handles[wire] = id;
+ }
+
if (wire->attributes.count(ID::init)) {
Const initval = wire->attributes.at(ID::init);
for (int i = 0; i < GetSize(sig) && i < GetSize(initval); i++)
@@ -144,7 +217,7 @@ struct SimInstance
Module *mod = module->design->module(cell->type);
if (mod != nullptr) {
- dirty_children.insert(new SimInstance(shared, mod, cell, this));
+ dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this));
}
for (auto &port : cell->connections()) {
@@ -157,16 +230,24 @@ struct SimInstance
}
}
- if (cell->type.in(ID($dff))) {
+ if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
+ FfData ff_data(nullptr, cell);
ff_state_t ff;
- ff.past_clock = State::Sx;
- ff.past_d = Const(State::Sx, cell->getParam(ID::WIDTH).as_int());
+ ff.past_d = Const(State::Sx, ff_data.width);
+ ff.past_ad = Const(State::Sx, ff_data.width);
+ ff.past_clk = State::Sx;
+ ff.past_ce = State::Sx;
+ ff.past_srst = State::Sx;
+ ff.data = ff_data;
ff_database[cell] = ff;
}
if (cell->is_mem_cell())
{
- mem_cells[cell] = cell->parameters.at(ID::MEMID).decode_string();
+ std::string name = cell->parameters.at(ID::MEMID).decode_string();
+ mem_cells[cell] = name;
+ if (shared->fst)
+ fst_memories[name] = shared->fst->getMemoryHandles(scope + "." + RTLIL::unescape_id(name));
}
if (cell->type.in(ID($assert), ID($cover), ID($assume))) {
formal_database.insert(cell);
@@ -177,11 +258,11 @@ struct SimInstance
{
for (auto &it : ff_database)
{
- Cell *cell = it.first;
ff_state_t &ff = it.second;
zinit(ff.past_d);
+ zinit(ff.past_ad);
- SigSpec qsig = cell->getPort(ID::Q);
+ SigSpec qsig = it.second.data.sig_q;
Const qdata = get_state(qsig);
zinit(qdata);
set_state(qsig, qdata);
@@ -253,6 +334,24 @@ struct SimInstance
return did_something;
}
+ void set_memory_state(IdString memid, Const addr, Const data)
+ {
+ auto &state = mem_database[memid];
+
+ int offset = (addr.as_int() - state.mem->start_offset) * state.mem->width;
+ for (int i = 0; i < GetSize(data); i++)
+ if (0 <= i+offset && i+offset < state.mem->size * state.mem->width)
+ state.data.bits[i+offset] = data.bits[i];
+ }
+
+ void set_memory_state_bit(IdString memid, int offset, State data)
+ {
+ auto &state = mem_database[memid];
+ if (offset >= state.mem->size * state.mem->width)
+ log_error("Addressing out of bounds bit %d/%d of memory %s\n", offset, state.mem->size * state.mem->width, log_id(memid));
+ state.data.bits[offset] = data;
+ }
+
void update_cell(Cell *cell)
{
if (ff_database.count(cell))
@@ -313,6 +412,12 @@ struct SimInstance
return;
}
+ // (A,S -> Y) cells
+ if (has_a && !has_b && !has_c && !has_d && has_s && has_y) {
+ set_state(sig_y, CellTypes::eval(cell, get_state(sig_a), get_state(sig_s)));
+ return;
+ }
+
// (A,B,S -> Y) cells
if (has_a && has_b && !has_c && !has_d && has_s && has_y) {
set_state(sig_y, CellTypes::eval(cell, get_state(sig_a), get_state(sig_b), get_state(sig_s)));
@@ -408,21 +513,62 @@ struct SimInstance
for (auto &it : ff_database)
{
- Cell *cell = it.first;
ff_state_t &ff = it.second;
-
- if (cell->type.in(ID($dff)))
- {
- bool clkpol = cell->getParam(ID::CLK_POLARITY).as_bool();
- State current_clock = get_state(cell->getPort(ID::CLK))[0];
-
- if (clkpol ? (ff.past_clock == State::S1 || current_clock != State::S1) :
- (ff.past_clock == State::S0 || current_clock != State::S0))
- continue;
-
- if (set_state(cell->getPort(ID::Q), ff.past_d))
- did_something = true;
+ FfData &ff_data = ff.data;
+
+ Const current_q = get_state(ff.data.sig_q);
+
+ if (ff_data.has_clk) {
+ // flip-flops
+ State current_clk = get_state(ff_data.sig_clk)[0];
+ if (ff_data.pol_clk ? (ff.past_clk == State::S0 && current_clk != State::S0) :
+ (ff.past_clk == State::S1 && current_clk != State::S1)) {
+ bool ce = ff.past_ce == (ff_data.pol_ce ? State::S1 : State::S0);
+ // set if no ce, or ce is enabled
+ if (!ff_data.has_ce || (ff_data.has_ce && ce)) {
+ current_q = ff.past_d;
+ }
+ // override if sync reset
+ if ((ff_data.has_srst) && (ff.past_srst == (ff_data.pol_srst ? State::S1 : State::S0)) &&
+ ((!ff_data.ce_over_srst) || (ff_data.ce_over_srst && ce))) {
+ current_q = ff_data.val_srst;
+ }
+ }
+ }
+ // async load
+ if (ff_data.has_aload) {
+ State current_aload = get_state(ff_data.sig_aload)[0];
+ if (current_aload == (ff_data.pol_aload ? State::S1 : State::S0)) {
+ current_q = ff_data.has_clk ? ff.past_ad : get_state(ff.data.sig_ad);
+ }
+ }
+ // async reset
+ if (ff_data.has_arst) {
+ State current_arst = get_state(ff_data.sig_arst)[0];
+ if (current_arst == (ff_data.pol_arst ? State::S1 : State::S0)) {
+ current_q = ff_data.val_arst;
+ }
+ }
+ // handle set/reset
+ if (ff.data.has_sr) {
+ Const current_clr = get_state(ff.data.sig_clr);
+ Const current_set = get_state(ff.data.sig_set);
+
+ for(int i=0;i<ff.past_d.size();i++) {
+ if (current_clr[i] == (ff_data.pol_clr ? State::S1 : State::S0)) {
+ current_q[i] = State::S0;
+ }
+ else if (current_set[i] == (ff_data.pol_set ? State::S1 : State::S0)) {
+ current_q[i] = State::S1;
+ }
+ }
+ }
+ if (ff_data.has_gclk) {
+ // $ff
+ current_q = ff.past_d;
}
+ if (set_state(ff_data.sig_q, current_q))
+ did_something = true;
}
for (auto &it : mem_database)
@@ -480,13 +626,22 @@ struct SimInstance
{
for (auto &it : ff_database)
{
- Cell *cell = it.first;
ff_state_t &ff = it.second;
- if (cell->type.in(ID($dff))) {
- ff.past_clock = get_state(cell->getPort(ID::CLK))[0];
- ff.past_d = get_state(cell->getPort(ID::D));
- }
+ if (ff.data.has_aload)
+ ff.past_ad = get_state(ff.data.sig_ad);
+
+ if (ff.data.has_clk || ff.data.has_gclk)
+ ff.past_d = get_state(ff.data.sig_d);
+
+ if (ff.data.has_clk)
+ ff.past_clk = get_state(ff.data.sig_clk)[0];
+
+ if (ff.data.has_ce)
+ ff.past_ce = get_state(ff.data.sig_ce)[0];
+
+ if (ff.data.has_srst)
+ ff.past_srst = get_state(ff.data.sig_srst)[0];
}
for (auto &it : mem_database)
@@ -537,8 +692,7 @@ struct SimInstance
for (auto &it : ff_database)
{
- Cell *cell = it.first;
- SigSpec sig_q = cell->getPort(ID::Q);
+ SigSpec sig_q = it.second.data.sig_q;
Const initval = get_state(sig_q);
for (int i = 0; i < GetSize(sig_q); i++)
@@ -568,28 +722,51 @@ struct SimInstance
it.second->writeback(wbmods);
}
- void write_vcd_header(std::ofstream &f, int &id)
+ void register_signals(int &id)
{
- f << stringf("$scope module %s $end\n", log_id(name()));
-
for (auto wire : module->wires())
{
if (shared->hide_internal && wire->name[0] == '$')
continue;
- f << stringf("$var wire %d n%d %s%s $end\n", GetSize(wire), id, wire->name[0] == '$' ? "\\" : "", log_id(wire));
- vcd_database[wire] = make_pair(id++, Const());
+ signal_database[wire] = make_pair(id, Const());
+ id++;
}
for (auto child : children)
- child.second->write_vcd_header(f, id);
+ child.second->register_signals(id);
+ }
+
+ void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(Wire*, int, bool)> register_signal)
+ {
+ enter_scope(name());
- f << stringf("$upscope $end\n");
+ dict<Wire*,bool> registers;
+ for (auto cell : module->cells())
+ {
+ if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
+ FfData ff_data(nullptr, cell);
+ SigSpec q = sigmap(ff_data.sig_q);
+ if (q.is_wire() && signal_database.count(q.as_wire()) != 0) {
+ registers[q.as_wire()] = true;
+ }
+ }
+ }
+
+ for (auto signal : signal_database)
+ {
+ register_signal(signal.first, signal.second.first, registers.count(signal.first)!=0);
+ }
+
+ for (auto child : children)
+ child.second->write_output_header(enter_scope, exit_scope, register_signal);
+
+ exit_scope();
}
- void write_vcd_step(std::ofstream &f)
+ void register_output_step_values(std::map<int,Const> *data)
{
- for (auto &it : vcd_database)
+ for (auto &it : signal_database)
{
Wire *wire = it.first;
Const value = get_state(wire);
@@ -599,66 +776,189 @@ struct SimInstance
continue;
it.second.second = value;
+ data->emplace(id, value);
+ }
+
+ for (auto child : children)
+ child.second->register_output_step_values(data);
+ }
+
+ bool setInitState()
+ {
+ bool did_something = false;
+ for(auto &item : fst_handles) {
+ if (item.second==0) continue; // Ignore signals not found
+ std::string v = shared->fst->valueOf(item.second);
+ did_something |= set_state(item.first, Const::from_string(v));
+ }
+ for (auto &it : ff_database)
+ {
+ ff_state_t &ff = it.second;
+ SigSpec dsig = it.second.data.sig_d;
+ Const value = get_state(dsig);
+ if (dsig.is_wire()) {
+ ff.past_d = value;
+ if (ff.data.has_aload)
+ ff.past_ad = value;
+ did_something |= true;
+ }
+ }
+ for (auto cell : module->cells())
+ {
+ if (cell->is_mem_cell()) {
+ std::string memid = cell->parameters.at(ID::MEMID).decode_string();
+ for (auto &data : fst_memories[memid])
+ {
+ std::string v = shared->fst->valueOf(data.second);
+ set_memory_state(memid, Const(data.first), Const::from_string(v));
+ }
+ }
+ }
+
+ for (auto child : children)
+ did_something |= child.second->setInitState();
+ return did_something;
+ }
- f << "b";
- for (int i = GetSize(value)-1; i >= 0; i--) {
- switch (value[i]) {
- case State::S0: f << "0"; break;
- case State::S1: f << "1"; break;
- case State::Sx: f << "x"; break;
- default: f << "z";
+ void addAdditionalInputs(std::map<Wire*,fstHandle> &inputs)
+ {
+ for (auto cell : module->cells())
+ {
+ if (cell->type.in(ID($anyseq))) {
+ SigSpec sig_y = sigmap(cell->getPort(ID::Y));
+ if (sig_y.is_wire()) {
+ bool found = false;
+ for(auto &item : fst_handles) {
+ if (item.second==0) continue; // Ignore signals not found
+ if (sig_y == sigmap(item.first)) {
+ inputs[sig_y.as_wire()] = item.second;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(sig_y.as_wire()->name)).c_str());
}
}
+ }
+ for (auto child : children)
+ child.second->addAdditionalInputs(inputs);
+ }
- f << stringf(" n%d\n", id);
+ void setState(dict<int, std::pair<SigBit,bool>> bits, std::string values)
+ {
+ for(auto bit : bits) {
+ if (bit.first >= GetSize(values))
+ log_error("Too few input data bits in file.\n");
+ switch(values.at(bit.first)) {
+ case '0': set_state(bit.second.first, bit.second.second ? State::S1 : State::S0); break;
+ case '1': set_state(bit.second.first, bit.second.second ? State::S0 : State::S1); break;
+ default: set_state(bit.second.first, State::Sx); break;
+ }
}
+ }
+
+ void setMemState(dict<int, std::pair<std::string,int>> bits, std::string values)
+ {
+ for(auto bit : bits) {
+ if (bit.first >= GetSize(values))
+ log_error("Too few input data bits in file.\n");
+ switch(values.at(bit.first)) {
+ case '0': set_memory_state_bit(bit.second.first, bit.second.second, State::S0); break;
+ case '1': set_memory_state_bit(bit.second.first, bit.second.second, State::S1); break;
+ default: set_memory_state_bit(bit.second.first, bit.second.second, State::Sx); break;
+ }
+ }
+ }
+ bool checkSignals()
+ {
+ bool retVal = false;
+ for(auto &item : fst_handles) {
+ if (item.second==0) continue; // Ignore signals not found
+ Const fst_val = Const::from_string(shared->fst->valueOf(item.second));
+ Const sim_val = get_state(item.first);
+ if (sim_val.size()!=fst_val.size()) {
+ log_warning("Signal '%s.%s' size is different in gold and gate.\n", scope.c_str(), log_id(item.first));
+ continue;
+ }
+ if (shared->sim_mode == SimulationMode::sim) {
+ // No checks performed when using stimulus
+ } else if (shared->sim_mode == SimulationMode::gate && !fst_val.is_fully_def()) { // FST data contains X
+ for(int i=0;i<fst_val.size();i++) {
+ if (fst_val[i]!=State::Sx && fst_val[i]!=sim_val[i]) {
+ log_warning("Signal '%s.%s' in file %s in simulation %s\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val));
+ retVal = true;
+ break;
+ }
+ }
+ } else if (shared->sim_mode == SimulationMode::gold && !sim_val.is_fully_def()) { // sim data contains X
+ for(int i=0;i<sim_val.size();i++) {
+ if (sim_val[i]!=State::Sx && fst_val[i]!=sim_val[i]) {
+ log_warning("Signal '%s.%s' in file %s in simulation %s\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val));
+ retVal = true;
+ break;
+ }
+ }
+ } else {
+ if (fst_val!=sim_val) {
+ log_warning("Signal '%s.%s' in file %s in simulation '%s'\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val));
+ retVal = true;
+ }
+ }
+ }
for (auto child : children)
- child.second->write_vcd_step(f);
+ retVal |= child.second->checkSignals();
+ return retVal;
}
};
struct SimWorker : SimShared
{
SimInstance *top = nullptr;
- std::ofstream vcdfile;
pool<IdString> clock, clockn, reset, resetn;
std::string timescale;
+ std::string sim_filename;
+ std::string map_filename;
+ std::string scope;
~SimWorker()
{
+ outputfiles.clear();
delete top;
}
- void write_vcd_header()
+ void register_signals()
{
- if (!vcdfile.is_open())
- return;
-
- vcdfile << stringf("$version %s $end\n", yosys_version_str);
-
- std::time_t t = std::time(nullptr);
- char mbstr[255];
- if (std::strftime(mbstr, sizeof(mbstr), "%c", std::localtime(&t))) {
- vcdfile << stringf("$date ") << mbstr << stringf(" $end\n");
- }
-
- if (!timescale.empty())
- vcdfile << stringf("$timescale %s $end\n", timescale.c_str());
-
int id = 1;
- top->write_vcd_header(vcdfile, id);
-
- vcdfile << stringf("$enddefinitions $end\n");
+ top->register_signals(id);
}
- void write_vcd_step(int t)
+ void register_output_step(int t)
{
- if (!vcdfile.is_open())
- return;
+ std::map<int,Const> data;
+ top->register_output_step_values(&data);
+ output_data.emplace_back(t, data);
+ }
- vcdfile << stringf("#%d\n", t);
- top->write_vcd_step(vcdfile);
+ void write_output_files()
+ {
+ std::map<int, bool> use_signal;
+ bool first = ignore_x;
+ for(auto& d : output_data)
+ {
+ if (first) {
+ for (auto &data : d.second)
+ use_signal[data.first] = !data.second.is_fully_undef();
+ first = false;
+ } else {
+ for (auto &data : d.second)
+ use_signal[data.first] = true;
+ }
+ if (!ignore_x) break;
+ }
+ for(auto& writer : outputfiles)
+ writer->write(use_signal);
}
void update()
@@ -699,11 +999,12 @@ struct SimWorker : SimShared
void run(Module *topmod, int numcycles)
{
log_assert(top == nullptr);
- top = new SimInstance(this, topmod);
+ top = new SimInstance(this, scope, topmod);
+ register_signals();
if (debug)
log("\n===== 0 =====\n");
- else
+ else if (verbose)
log("Simulating cycle 0.\n");
set_inports(reset, State::S1);
@@ -714,24 +1015,24 @@ struct SimWorker : SimShared
update();
- write_vcd_header();
- write_vcd_step(0);
+ register_output_step(0);
for (int cycle = 0; cycle < numcycles; cycle++)
{
if (debug)
log("\n===== %d =====\n", 10*cycle + 5);
-
+ else if (verbose)
+ log("Simulating cycle %d.\n", (cycle*2)+1);
set_inports(clock, State::S0);
set_inports(clockn, State::S1);
update();
- write_vcd_step(10*cycle + 5);
+ register_output_step(10*cycle + 5);
if (debug)
log("\n===== %d =====\n", 10*cycle + 10);
- else
- log("Simulating cycle %d.\n", cycle+1);
+ else if (verbose)
+ log("Simulating cycle %d.\n", (cycle*2)+2);
set_inports(clock, State::S1);
set_inports(clockn, State::S0);
@@ -742,18 +1043,878 @@ struct SimWorker : SimShared
}
update();
- write_vcd_step(10*cycle + 10);
+ register_output_step(10*cycle + 10);
+ }
+
+ register_output_step(10*numcycles + 2);
+
+ write_output_files();
+
+ if (writeback) {
+ pool<Module*> wbmods;
+ top->writeback(wbmods);
+ }
+ }
+
+ void run_cosim_fst(Module *topmod, int numcycles)
+ {
+ log_assert(top == nullptr);
+ fst = new FstData(sim_filename);
+
+ if (scope.empty())
+ log_error("Scope must be defined for co-simulation.\n");
+
+ top = new SimInstance(this, scope, topmod);
+ register_signals();
+
+ std::vector<fstHandle> fst_clock;
+
+ for (auto portname : clock)
+ {
+ Wire *w = topmod->wire(portname);
+ if (!w)
+ log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
+ if (!w->port_input)
+ log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
+ if (id==0)
+ log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname));
+ fst_clock.push_back(id);
+ }
+ for (auto portname : clockn)
+ {
+ Wire *w = topmod->wire(portname);
+ if (!w)
+ log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
+ if (!w->port_input)
+ log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
+ if (id==0)
+ log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname));
+ fst_clock.push_back(id);
}
- write_vcd_step(10*numcycles + 2);
+ SigMap sigmap(topmod);
+ std::map<Wire*,fstHandle> inputs;
+
+ for (auto wire : topmod->wires()) {
+ if (wire->port_input) {
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name));
+ if (id==0)
+ log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)).c_str());
+ inputs[wire] = id;
+ }
+ }
+
+ top->addAdditionalInputs(inputs);
+
+ uint64_t startCount = 0;
+ uint64_t stopCount = 0;
+ if (start_time==0) {
+ if (start_time < fst->getStartTime())
+ log_warning("Start time is before simulation file start time\n");
+ startCount = fst->getStartTime();
+ } else if (start_time==-1)
+ startCount = fst->getEndTime();
+ else {
+ startCount = start_time / fst->getTimescale();
+ if (startCount > fst->getEndTime()) {
+ startCount = fst->getEndTime();
+ log_warning("Start time is after simulation file end time\n");
+ }
+ }
+ if (stop_time==0) {
+ if (stop_time < fst->getStartTime())
+ log_warning("Stop time is before simulation file start time\n");
+ stopCount = fst->getStartTime();
+ } else if (stop_time==-1)
+ stopCount = fst->getEndTime();
+ else {
+ stopCount = stop_time / fst->getTimescale();
+ if (stopCount > fst->getEndTime()) {
+ stopCount = fst->getEndTime();
+ log_warning("Stop time is after simulation file end time\n");
+ }
+ }
+ if (stopCount<startCount) {
+ log_error("Stop time is before start time\n");
+ }
+
+ bool initial = true;
+ int cycle = 0;
+ log("Co-simulation from %lu%s to %lu%s", (unsigned long)startCount, fst->getTimescaleString(), (unsigned long)stopCount, fst->getTimescaleString());
+ if (cycles_set)
+ log(" for %d clock cycle(s)",numcycles);
+ log("\n");
+ bool all_samples = fst_clock.empty();
+
+ try {
+ fst->reconstructAllAtTimes(fst_clock, startCount, stopCount, [&](uint64_t time) {
+ if (verbose)
+ log("Co-simulating %s %d [%lu%s].\n", (all_samples ? "sample" : "cycle"), cycle, (unsigned long)time, fst->getTimescaleString());
+ bool did_something = false;
+ for(auto &item : inputs) {
+ std::string v = fst->valueOf(item.second);
+ did_something |= top->set_state(item.first, Const::from_string(v));
+ }
+
+ if (initial) {
+ did_something |= top->setInitState();
+ initial = false;
+ }
+ if (did_something)
+ update();
+ register_output_step(time);
+
+ bool status = top->checkSignals();
+ if (status)
+ log_error("Signal difference\n");
+ cycle++;
+
+ // Limit to number of cycles if provided
+ if (cycles_set && cycle > numcycles *2)
+ throw fst_end_of_data_exception();
+ if (time==stopCount)
+ throw fst_end_of_data_exception();
+ });
+ } catch(fst_end_of_data_exception) {
+ // end of data detected
+ }
+
+ write_output_files();
if (writeback) {
pool<Module*> wbmods;
top->writeback(wbmods);
}
+ delete fst;
+ }
+
+ std::string cell_name(std::string const & name)
+ {
+ size_t pos = name.find_last_of("[");
+ if (pos!=std::string::npos)
+ return name.substr(0, pos);
+ return name;
+ }
+
+ int mem_cell_addr(std::string const & name)
+ {
+ size_t pos = name.find_last_of("[");
+ return atoi(name.substr(pos+1).c_str());
+ }
+
+ void run_cosim_aiger_witness(Module *topmod)
+ {
+ log_assert(top == nullptr);
+ if (!multiclock && (clock.size()+clockn.size())==0)
+ log_error("Clock signal must be specified.\n");
+ if (multiclock && (clock.size()+clockn.size())>0)
+ log_error("For multiclock witness there should be no clock signal.\n");
+
+ top = new SimInstance(this, scope, topmod);
+ register_signals();
+
+ std::ifstream mf(map_filename);
+ std::string type, symbol;
+ int variable, index;
+ dict<int, std::pair<SigBit,bool>> inputs, inits, latches;
+ dict<int, std::pair<std::string,int>> mem_inits, mem_latches;
+ if (mf.fail())
+ log_cmd_error("Not able to read AIGER witness map file.\n");
+ while (mf >> type >> variable >> index >> symbol) {
+ RTLIL::IdString escaped_s = RTLIL::escape_id(symbol);
+ Wire *w = topmod->wire(escaped_s);
+ if (!w) {
+ escaped_s = RTLIL::escape_id(cell_name(symbol));
+ Cell *c = topmod->cell(escaped_s);
+ if (!c)
+ log_warning("Wire/cell %s not present in module %s\n",symbol.c_str(),log_id(topmod));
+
+ if (c->is_mem_cell()) {
+ std::string memid = c->parameters.at(ID::MEMID).decode_string();
+ auto &state = top->mem_database[memid];
+
+ int offset = (mem_cell_addr(symbol) - state.mem->start_offset) * state.mem->width + index;
+ if (type == "init")
+ mem_inits[variable] = { memid, offset };
+ else if (type == "latch")
+ mem_latches[variable] = { memid, offset };
+ else
+ log_error("Map file addressing cell %s as type %s\n", symbol.c_str(), type.c_str());
+ } else {
+ log_error("Cell %s in map file is not memory cell\n", symbol.c_str());
+ }
+ } else {
+ if (index < w->start_offset || index > w->start_offset + w->width)
+ log_error("Index %d for wire %s is out of range\n", index, log_signal(w));
+ if (type == "input") {
+ inputs[variable] = {SigBit(w,index-w->start_offset), false};
+ } else if (type == "init") {
+ inits[variable] = {SigBit(w,index-w->start_offset), false};
+ } else if (type == "latch") {
+ latches[variable] = {SigBit(w,index-w->start_offset), false};
+ } else if (type == "invlatch") {
+ latches[variable] = {SigBit(w,index-w->start_offset), true};
+ }
+ }
+ }
+
+ std::ifstream f;
+ f.open(sim_filename.c_str());
+ if (f.fail() || GetSize(sim_filename) == 0)
+ log_error("Can not open file `%s`\n", sim_filename.c_str());
+
+ int state = 0;
+ std::string status;
+ int cycle = 0;
+
+ while (!f.eof())
+ {
+ std::string line;
+ std::getline(f, line);
+ if (line.size()==0 || line[0]=='#' || line[0]=='c' || line[0]=='f' || line[0]=='u') continue;
+ if (line[0]=='.') break;
+ if (state==0 && line.size()!=1) {
+ // old format detected, latch data
+ state = 2;
+ }
+ if (state==1 && line[0]!='b' && line[0]!='j') {
+ // was old format but with 1 bit latch
+ top->setState(latches, status);
+ state = 3;
+ }
+
+ switch(state)
+ {
+ case 0:
+ status = line;
+ state = 1;
+ break;
+ case 1:
+ state = 2;
+ break;
+ case 2:
+ top->setState(latches, line);
+ top->setMemState(mem_latches, line);
+ state = 3;
+ break;
+ default:
+ if (verbose)
+ log("Simulating cycle %d.\n", cycle);
+ top->setState(inputs, line);
+ if (cycle) {
+ set_inports(clock, State::S1);
+ set_inports(clockn, State::S0);
+ } else {
+ top->setState(inits, line);
+ top->setMemState(mem_inits, line);
+ set_inports(clock, State::S0);
+ set_inports(clockn, State::S1);
+ }
+ update();
+ register_output_step(10*cycle);
+ if (!multiclock && cycle) {
+ set_inports(clock, State::S0);
+ set_inports(clockn, State::S1);
+ update();
+ register_output_step(10*cycle + 5);
+ }
+ cycle++;
+ break;
+ }
+ }
+ register_output_step(10*cycle);
+ write_output_files();
+ }
+
+ std::vector<std::string> split(std::string text, const char *delim)
+ {
+ std::vector<std::string> list;
+ char *p = strdup(text.c_str());
+ char *t = strtok(p, delim);
+ while (t != NULL) {
+ list.push_back(t);
+ t = strtok(NULL, delim);
+ }
+ free(p);
+ return list;
+ }
+
+ std::string signal_name(std::string const & name)
+ {
+ size_t pos = name.find_first_of("@");
+ if (pos==std::string::npos) {
+ pos = name.find_first_of("#");
+ if (pos==std::string::npos)
+ log_error("Line does not contain proper signal name `%s`\n", name.c_str());
+ }
+ return name.substr(0, pos);
+ }
+
+ void run_cosim_btor2_witness(Module *topmod)
+ {
+ log_assert(top == nullptr);
+ if (!multiclock && (clock.size()+clockn.size())==0)
+ log_error("Clock signal must be specified.\n");
+ if (multiclock && (clock.size()+clockn.size())>0)
+ log_error("For multiclock witness there should be no clock signal.\n");
+ std::ifstream f;
+ f.open(sim_filename.c_str());
+ if (f.fail() || GetSize(sim_filename) == 0)
+ log_error("Can not open file `%s`\n", sim_filename.c_str());
+
+ int state = 0;
+ int cycle = 0;
+ top = new SimInstance(this, scope, topmod);
+ register_signals();
+ int prev_cycle = 0;
+ int curr_cycle = 0;
+ std::vector<std::string> parts;
+ size_t len = 0;
+ while (!f.eof())
+ {
+ std::string line;
+ std::getline(f, line);
+ if (line.size()==0) continue;
+
+ if (line[0]=='#' || line[0]=='@' || line[0]=='.') {
+ if (line[0]!='.')
+ curr_cycle = atoi(line.c_str()+1);
+ else
+ curr_cycle = -1; // force detect change
+
+ if (curr_cycle != prev_cycle) {
+ if (verbose)
+ log("Simulating cycle %d.\n", cycle);
+ set_inports(clock, State::S1);
+ set_inports(clockn, State::S0);
+ update();
+ register_output_step(10*cycle+0);
+ if (!multiclock) {
+ set_inports(clock, State::S0);
+ set_inports(clockn, State::S1);
+ update();
+ register_output_step(10*cycle+5);
+ }
+ cycle++;
+ prev_cycle = curr_cycle;
+ }
+ if (line[0]=='.') break;
+ continue;
+ }
+
+ switch(state)
+ {
+ case 0:
+ if (line=="sat")
+ state = 1;
+ break;
+ case 1:
+ if (line[0]=='b' || line[0]=='j')
+ state = 2;
+ else
+ log_error("Line does not contain property.\n");
+ break;
+ default: // set state or inputs
+ parts = split(line, " ");
+ len = parts.size();
+ if (len<3 || len>4)
+ log_error("Invalid set state line content.\n");
+
+ RTLIL::IdString escaped_s = RTLIL::escape_id(signal_name(parts[len-1]));
+ if (len==3) {
+ Wire *w = topmod->wire(escaped_s);
+ if (!w) {
+ Cell *c = topmod->cell(escaped_s);
+ if (!c)
+ log_warning("Wire/cell %s not present in module %s\n",log_id(escaped_s),log_id(topmod));
+ else if (c->type.in(ID($anyconst), ID($anyseq))) {
+ SigSpec sig_y= c->getPort(ID::Y);
+ if ((int)parts[1].size() != GetSize(sig_y))
+ log_error("Size of wire %s is different than provided data.\n", log_signal(sig_y));
+ top->set_state(sig_y, Const::from_string(parts[1]));
+ }
+ } else {
+ if ((int)parts[1].size() != w->width)
+ log_error("Size of wire %s is different than provided data.\n", log_signal(w));
+ top->set_state(w, Const::from_string(parts[1]));
+ }
+ } else {
+ Cell *c = topmod->cell(escaped_s);
+ if (!c)
+ log_error("Cell %s not present in module %s\n",log_id(escaped_s),log_id(topmod));
+ if (!c->is_mem_cell())
+ log_error("Cell %s is not memory cell in module %s\n",log_id(escaped_s),log_id(topmod));
+
+ Const addr = Const::from_string(parts[1].substr(1,parts[1].size()-2));
+ Const data = Const::from_string(parts[2]);
+ top->set_memory_state(c->parameters.at(ID::MEMID).decode_string(), addr, data);
+ }
+ break;
+ }
+ }
+ register_output_step(10*cycle);
+ write_output_files();
+ }
+
+ std::string define_signal(Wire *wire)
+ {
+ std::stringstream f;
+
+ if (wire->width==1)
+ f << stringf("%s", RTLIL::unescape_id(wire->name).c_str());
+ else
+ if (wire->upto)
+ f << stringf("[%d:%d] %s", wire->start_offset, wire->width - 1 + wire->start_offset, RTLIL::unescape_id(wire->name).c_str());
+ else
+ f << stringf("[%d:%d] %s", wire->width - 1 + wire->start_offset, wire->start_offset, RTLIL::unescape_id(wire->name).c_str());
+ return f.str();
+ }
+
+ std::string signal_list(std::map<Wire*,fstHandle> &signals)
+ {
+ std::stringstream f;
+ for(auto item=signals.begin();item!=signals.end();item++)
+ f << stringf("%c%s", (item==signals.begin() ? ' ' : ','), RTLIL::unescape_id(item->first->name).c_str());
+ return f.str();
+ }
+
+ void generate_tb(Module *topmod, std::string tb_filename, int numcycles)
+ {
+ fst = new FstData(sim_filename);
+
+ if (scope.empty())
+ log_error("Scope must be defined for co-simulation.\n");
+
+ if ((clock.size()+clockn.size())==0)
+ log_error("Clock signal must be specified.\n");
+
+ std::vector<fstHandle> fst_clock;
+ std::map<Wire*,fstHandle> clocks;
+
+ for (auto portname : clock)
+ {
+ Wire *w = topmod->wire(portname);
+ if (!w)
+ log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
+ if (!w->port_input)
+ log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
+ if (id==0)
+ log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname));
+ fst_clock.push_back(id);
+ clocks[w] = id;
+ }
+ for (auto portname : clockn)
+ {
+ Wire *w = topmod->wire(portname);
+ if (!w)
+ log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
+ if (!w->port_input)
+ log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
+ if (id==0)
+ log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname));
+ fst_clock.push_back(id);
+ clocks[w] = id;
+ }
+
+ SigMap sigmap(topmod);
+ std::map<Wire*,fstHandle> inputs;
+ std::map<Wire*,fstHandle> outputs;
+
+ for (auto wire : topmod->wires()) {
+ fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name));
+ if (id==0 && (wire->port_input || wire->port_output))
+ log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)).c_str());
+ if (wire->port_input)
+ if (clocks.find(wire)==clocks.end())
+ inputs[wire] = id;
+ if (wire->port_output)
+ outputs[wire] = id;
+ }
+
+ uint64_t startCount = 0;
+ uint64_t stopCount = 0;
+ if (start_time==0) {
+ if (start_time < fst->getStartTime())
+ log_warning("Start time is before simulation file start time\n");
+ startCount = fst->getStartTime();
+ } else if (start_time==-1)
+ startCount = fst->getEndTime();
+ else {
+ startCount = start_time / fst->getTimescale();
+ if (startCount > fst->getEndTime()) {
+ startCount = fst->getEndTime();
+ log_warning("Start time is after simulation file end time\n");
+ }
+ }
+ if (stop_time==0) {
+ if (stop_time < fst->getStartTime())
+ log_warning("Stop time is before simulation file start time\n");
+ stopCount = fst->getStartTime();
+ } else if (stop_time==-1)
+ stopCount = fst->getEndTime();
+ else {
+ stopCount = stop_time / fst->getTimescale();
+ if (stopCount > fst->getEndTime()) {
+ stopCount = fst->getEndTime();
+ log_warning("Stop time is after simulation file end time\n");
+ }
+ }
+ if (stopCount<startCount) {
+ log_error("Stop time is before start time\n");
+ }
+
+ int cycle = 0;
+ log("Generate testbench data from %lu%s to %lu%s", (unsigned long)startCount, fst->getTimescaleString(), (unsigned long)stopCount, fst->getTimescaleString());
+ if (cycles_set)
+ log(" for %d clock cycle(s)",numcycles);
+ log("\n");
+
+ std::stringstream f;
+ f << stringf("`timescale 1%s/1%s\n", fst->getTimescaleString(),fst->getTimescaleString());
+ f << stringf("module %s();\n",tb_filename.c_str());
+ int clk_len = 0;
+ int inputs_len = 0;
+ int outputs_len = 0;
+ for(auto &item : clocks) {
+ clk_len += item.first->width;
+ f << "\treg " << define_signal(item.first) << ";\n";
+ }
+ for(auto &item : inputs) {
+ inputs_len += item.first->width;
+ f << "\treg " << define_signal(item.first) << ";\n";
+ }
+ for(auto &item : outputs) {
+ outputs_len += item.first->width;
+ f << "\twire " << define_signal(item.first) << ";\n";
+ }
+ int data_len = clk_len + inputs_len + outputs_len + 32;
+ f << "\n";
+ f << stringf("\t%s uut(",RTLIL::unescape_id(topmod->name).c_str());
+ for(auto item=clocks.begin();item!=clocks.end();item++)
+ f << stringf("%c.%s(%s)", (item==clocks.begin() ? ' ' : ','), RTLIL::unescape_id(item->first->name).c_str(), RTLIL::unescape_id(item->first->name).c_str());
+ for(auto &item : inputs)
+ f << stringf(",.%s(%s)", RTLIL::unescape_id(item.first->name).c_str(), RTLIL::unescape_id(item.first->name).c_str());
+ for(auto &item : outputs)
+ f << stringf(",.%s(%s)", RTLIL::unescape_id(item.first->name).c_str(), RTLIL::unescape_id(item.first->name).c_str());
+ f << ");\n";
+ f << "\n";
+ f << "\tinteger i;\n";
+ uint64_t prev_time = startCount;
+ log("Writing data to `%s`\n", (tb_filename+".txt").c_str());
+ std::ofstream data_file(tb_filename+".txt");
+ std::stringstream initstate;
+ try {
+ fst->reconstructAllAtTimes(fst_clock, startCount, stopCount, [&](uint64_t time) {
+ for(auto &item : clocks)
+ data_file << stringf("%s",fst->valueOf(item.second).c_str());
+ for(auto &item : inputs)
+ data_file << stringf("%s",fst->valueOf(item.second).c_str());
+ for(auto &item : outputs)
+ data_file << stringf("%s",fst->valueOf(item.second).c_str());
+ data_file << stringf("%s\n",Const(time-prev_time).as_string().c_str());
+
+ if (time==startCount) {
+ // initial state
+ for(auto var : fst->getVars()) {
+ if (var.is_reg && !Const::from_string(fst->valueOf(var.id).c_str()).is_fully_undef()) {
+ if (var.scope == scope) {
+ initstate << stringf("\t\tuut.%s = %d'b%s;\n", var.name.c_str(), var.width, fst->valueOf(var.id).c_str());
+ } else if (var.scope.find(scope+".")==0) {
+ initstate << stringf("\t\tuut.%s.%s = %d'b%s;\n",var.scope.substr(scope.size()+1).c_str(), var.name.c_str(), var.width, fst->valueOf(var.id).c_str());
+ }
+ }
+ }
+ }
+ cycle++;
+ prev_time = time;
+
+ // Limit to number of cycles if provided
+ if (cycles_set && cycle > numcycles *2)
+ throw fst_end_of_data_exception();
+ if (time==stopCount)
+ throw fst_end_of_data_exception();
+ });
+ } catch(fst_end_of_data_exception) {
+ // end of data detected
+ }
+
+ f << stringf("\treg [0:%d] data [0:%d];\n", data_len-1, cycle-1);
+ f << "\tinitial begin;\n";
+ f << stringf("\t\t$dumpfile(\"%s\");\n",tb_filename.c_str());
+ f << stringf("\t\t$dumpvars(0,%s);\n",tb_filename.c_str());
+ f << initstate.str();
+ f << stringf("\t\t$readmemb(\"%s.txt\", data);\n",tb_filename.c_str());
+
+ f << stringf("\t\t#(data[0][%d:%d]);\n", data_len-32, data_len-1);
+ f << stringf("\t\t{%s } = data[0][%d:%d];\n", signal_list(clocks).c_str(), 0, clk_len-1);
+ f << stringf("\t\t{%s } <= data[0][%d:%d];\n", signal_list(inputs).c_str(), clk_len, clk_len+inputs_len-1);
+
+ f << stringf("\t\tfor (i = 1; i < %d; i++) begin\n",cycle);
+
+ f << stringf("\t\t\t#(data[i][%d:%d]);\n", data_len-32, data_len-1);
+ f << stringf("\t\t\t{%s } = data[i][%d:%d];\n", signal_list(clocks).c_str(), 0, clk_len-1);
+ f << stringf("\t\t\t{%s } <= data[i][%d:%d];\n", signal_list(inputs).c_str(), clk_len, clk_len+inputs_len-1);
+
+ f << stringf("\t\t\tif ({%s } != data[i-1][%d:%d]) begin\n", signal_list(outputs).c_str(), clk_len+inputs_len, clk_len+inputs_len+outputs_len-1);
+ f << "\t\t\t\t$error(\"Signal difference detected\\n\");\n";
+ f << "\t\t\tend\n";
+
+ f << "\t\tend\n";
+
+ f << "\t\t$finish;\n";
+ f << "\tend\n";
+ f << "endmodule\n";
+
+ log("Writing testbench to `%s`\n", (tb_filename+".v").c_str());
+ std::ofstream tb_file(tb_filename+".v");
+ tb_file << f.str();
+
+ delete fst;
}
};
+struct VCDWriter : public OutputWriter
+{
+ VCDWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) {
+ vcdfile.open(filename.c_str());
+ }
+
+ void write(std::map<int, bool> &use_signal) override
+ {
+ if (!vcdfile.is_open()) return;
+ vcdfile << stringf("$version %s $end\n", worker->date ? yosys_version_str : "Yosys");
+
+ if (worker->date) {
+ std::time_t t = std::time(nullptr);
+ char mbstr[255];
+ if (std::strftime(mbstr, sizeof(mbstr), "%c", std::localtime(&t))) {
+ vcdfile << stringf("$date ") << mbstr << stringf(" $end\n");
+ }
+ }
+
+ if (!worker->timescale.empty())
+ vcdfile << stringf("$timescale %s $end\n", worker->timescale.c_str());
+
+ worker->top->write_output_header(
+ [this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); },
+ [this]() { vcdfile << stringf("$upscope $end\n");},
+ [this,use_signal](Wire *wire, int id, bool is_reg) { if (use_signal.at(id)) vcdfile << stringf("$var %s %d n%d %s%s $end\n", is_reg ? "reg" : "wire", GetSize(wire), id, wire->name[0] == '$' ? "\\" : "", log_id(wire)); }
+ );
+
+ vcdfile << stringf("$enddefinitions $end\n");
+
+ for(auto& d : worker->output_data)
+ {
+ vcdfile << stringf("#%d\n", d.first);
+ for (auto &data : d.second)
+ {
+ if (!use_signal.at(data.first)) continue;
+ Const value = data.second;
+ vcdfile << "b";
+ for (int i = GetSize(value)-1; i >= 0; i--) {
+ switch (value[i]) {
+ case State::S0: vcdfile << "0"; break;
+ case State::S1: vcdfile << "1"; break;
+ case State::Sx: vcdfile << "x"; break;
+ default: vcdfile << "z";
+ }
+ }
+ vcdfile << stringf(" n%d\n", data.first);
+ }
+ }
+ }
+
+ std::ofstream vcdfile;
+};
+
+struct FSTWriter : public OutputWriter
+{
+ FSTWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) {
+ fstfile = (struct fstContext *)fstWriterCreate(filename.c_str(),1);
+ }
+
+ virtual ~FSTWriter()
+ {
+ fstWriterClose(fstfile);
+ }
+
+ void write(std::map<int, bool> &use_signal) override
+ {
+ if (!fstfile) return;
+ std::time_t t = std::time(nullptr);
+ fstWriterSetVersion(fstfile, worker->date ? yosys_version_str : "Yosys");
+ if (worker->date)
+ fstWriterSetDate(fstfile, asctime(std::localtime(&t)));
+ else
+ fstWriterSetDate(fstfile, "");
+ if (!worker->timescale.empty())
+ fstWriterSetTimescaleFromString(fstfile, worker->timescale.c_str());
+
+ fstWriterSetPackType(fstfile, FST_WR_PT_FASTLZ);
+ fstWriterSetRepackOnClose(fstfile, 1);
+
+ worker->top->write_output_header(
+ [this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); },
+ [this]() { fstWriterSetUpscope(fstfile); },
+ [this,use_signal](Wire *wire, int id, bool is_reg) {
+ if (!use_signal.at(id)) return;
+ fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, GetSize(wire),
+ stringf("%s%s", wire->name[0] == '$' ? "\\" : "", log_id(wire)).c_str(), 0);
+
+ mapping.emplace(id, fst_id);
+ }
+ );
+
+ for(auto& d : worker->output_data)
+ {
+ fstWriterEmitTimeChange(fstfile, d.first);
+ for (auto &data : d.second)
+ {
+ if (!use_signal.at(data.first)) continue;
+ Const value = data.second;
+ std::stringstream ss;
+ for (int i = GetSize(value)-1; i >= 0; i--) {
+ switch (value[i]) {
+ case State::S0: ss << "0"; break;
+ case State::S1: ss << "1"; break;
+ case State::Sx: ss << "x"; break;
+ default: ss << "z";
+ }
+ }
+ fstWriterEmitValueChange(fstfile, mapping[data.first], ss.str().c_str());
+ }
+ }
+ }
+
+ struct fstContext *fstfile = nullptr;
+ std::map<int,fstHandle> mapping;
+};
+
+struct AIWWriter : public OutputWriter
+{
+ AIWWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) {
+ aiwfile.open(filename.c_str());
+ }
+
+ virtual ~AIWWriter()
+ {
+ aiwfile << '.' << '\n';
+ }
+
+ void write(std::map<int, bool> &) override
+ {
+ if (!aiwfile.is_open()) return;
+ if (worker->map_filename.empty())
+ log_cmd_error("For AIGER witness file map parameter is mandatory.\n");
+
+ std::ifstream mf(worker->map_filename);
+ std::string type, symbol;
+ int variable, index;
+ int max_input = 0;
+ if (mf.fail())
+ log_cmd_error("Not able to read AIGER witness map file.\n");
+ while (mf >> type >> variable >> index >> symbol) {
+ RTLIL::IdString escaped_s = RTLIL::escape_id(symbol);
+ Wire *w = worker->top->module->wire(escaped_s);
+ if (!w)
+ log_error("Wire %s not present in module %s\n",log_id(escaped_s),log_id(worker->top->module));
+ if (index < w->start_offset || index > w->start_offset + w->width)
+ log_error("Index %d for wire %s is out of range\n", index, log_signal(w));
+ if (type == "input") {
+ aiw_inputs[variable] = SigBit(w,index-w->start_offset);
+ if (worker->clock.count(escaped_s)) {
+ clocks[variable] = true;
+ }
+ if (worker->clockn.count(escaped_s)) {
+ clocks[variable] = false;
+ }
+ max_input = max(max_input,variable);
+ } else if (type == "init") {
+ aiw_inits[variable] = SigBit(w,index-w->start_offset);
+ max_input = max(max_input,variable);
+ } else if (type == "latch") {
+ aiw_latches[variable] = {SigBit(w,index-w->start_offset), false};
+ } else if (type == "invlatch") {
+ aiw_latches[variable] = {SigBit(w,index-w->start_offset), true};
+ }
+ }
+
+ worker->top->write_output_header(
+ [](IdString) {},
+ []() {},
+ [this](Wire *wire, int id, bool) { mapping[wire] = id; }
+ );
+
+ std::map<int, Yosys::RTLIL::Const> current;
+ bool first = true;
+ for (auto iter = worker->output_data.begin(); iter != std::prev(worker->output_data.end()); ++iter)
+ {
+ auto& d = *iter;
+ for (auto &data : d.second)
+ {
+ current[data.first] = data.second;
+ }
+ if (first) {
+ for (int i = 0;; i++)
+ {
+ if (aiw_latches.count(i)) {
+ aiwfile << '0';
+ continue;
+ }
+ aiwfile << '\n';
+ break;
+ }
+ first = false;
+ }
+
+ bool skip = false;
+ for (auto it : clocks)
+ {
+ auto val = it.second ? State::S1 : State::S0;
+ SigBit bit = aiw_inputs.at(it.first);
+ auto v = current[mapping[bit.wire]].bits.at(bit.offset);
+ if (v == val)
+ skip = true;
+ }
+ if (skip)
+ continue;
+ for (int i = 0; i <= max_input; i++)
+ {
+ if (aiw_inputs.count(i)) {
+ SigBit bit = aiw_inputs.at(i);
+ auto v = current[mapping[bit.wire]].bits.at(bit.offset);
+ if (v == State::S1)
+ aiwfile << '1';
+ else
+ aiwfile << '0';
+ continue;
+ }
+ if (aiw_inits.count(i)) {
+ SigBit bit = aiw_inits.at(i);
+ auto v = current[mapping[bit.wire]].bits.at(bit.offset);
+ if (v == State::S1)
+ aiwfile << '1';
+ else
+ aiwfile << '0';
+ continue;
+ }
+ aiwfile << '0';
+ }
+ aiwfile << '\n';
+ }
+ }
+
+ std::ofstream aiwfile;
+ dict<int, std::pair<SigBit, bool>> aiw_latches;
+ dict<int, SigBit> aiw_inputs, aiw_inits;
+ dict<int, bool> clocks;
+ std::map<Wire*,int> mapping;
+};
+
struct SimPass : public Pass {
SimPass() : Pass("sim", "simulate the circuit") { }
void help() override
@@ -767,12 +1928,28 @@ struct SimPass : public Pass {
log(" -vcd <filename>\n");
log(" write the simulation results to the given VCD file\n");
log("\n");
+ log(" -fst <filename>\n");
+ log(" write the simulation results to the given FST file\n");
+ log("\n");
+ log(" -aiw <filename>\n");
+ log(" write the simulation results to an AIGER witness file\n");
+ log(" (requires a *.aim file via -map)\n");
+ log("\n");
+ log(" -x\n");
+ log(" ignore constant x outputs in simulation file.\n");
+ log("\n");
+ log(" -date\n");
+ log(" include date and full version info in output.\n");
+ log("\n");
log(" -clock <portname>\n");
log(" name of top-level clock input\n");
log("\n");
log(" -clockn <portname>\n");
log(" name of top-level clock input (inverse polarity)\n");
log("\n");
+ log(" -multiclock\n");
+ log(" mark that witness file is multiclock.\n");
+ log("\n");
log(" -reset <portname>\n");
log(" name of top-level reset input (active high)\n");
log("\n");
@@ -789,22 +1966,64 @@ struct SimPass : public Pass {
log(" include the specified timescale declaration in the vcd\n");
log("\n");
log(" -n <integer>\n");
- log(" number of cycles to simulate (default: 20)\n");
+ log(" number of clock cycles to simulate (default: 20)\n");
log("\n");
log(" -a\n");
- log(" include all nets in VCD output, not just those with public names\n");
+ log(" use all nets in VCD/FST operations, not just those with public names\n");
log("\n");
log(" -w\n");
log(" writeback mode: use final simulation state as new init state\n");
log("\n");
+ log(" -r\n");
+ log(" read simulation results file (file formats supported: FST, VCD, AIW and WIT)\n");
+ log(" VCD support requires vcd2fst external tool to be present\n");
+ log("\n");
+ log(" -map <filename>\n");
+ log(" read file with port and latch symbols, needed for AIGER witness input\n");
+ log("\n");
+ log(" -scope <name>\n");
+ log(" scope of simulation top model\n");
+ log("\n");
+ log(" -at <time>\n");
+ log(" sets start and stop time\n");
+ log("\n");
+ log(" -start <time>\n");
+ log(" start co-simulation in arbitary time (default 0)\n");
+ log("\n");
+ log(" -stop <time>\n");
+ log(" stop co-simulation in arbitary time (default END)\n");
+ log("\n");
+ log(" -sim\n");
+ log(" simulation with stimulus from FST (default)\n");
+ log("\n");
+ log(" -sim-cmp\n");
+ log(" co-simulation expect exact match\n");
+ log("\n");
+ log(" -sim-gold\n");
+ log(" co-simulation, x in simulation can match any value in FST\n");
+ log("\n");
+ log(" -sim-gate\n");
+ log(" co-simulation, x in FST can match any value in simulation\n");
+ log("\n");
+ log(" -q\n");
+ log(" disable per-cycle/sample log message\n");
+ log("\n");
log(" -d\n");
log(" enable debug output\n");
log("\n");
}
+
+
+ static std::string file_base_name(std::string const & path)
+ {
+ return path.substr(path.find_last_of("/\\") + 1);
+ }
+
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
SimWorker worker;
int numcycles = 20;
+ bool start_set = false, stop_set = false, at_set = false;
log_header(design, "Executing SIM pass (simulate the circuit).\n");
@@ -813,11 +2032,24 @@ struct SimPass : public Pass {
if (args[argidx] == "-vcd" && argidx+1 < args.size()) {
std::string vcd_filename = args[++argidx];
rewrite_filename(vcd_filename);
- worker.vcdfile.open(vcd_filename.c_str());
+ worker.outputfiles.emplace_back(std::unique_ptr<VCDWriter>(new VCDWriter(&worker, vcd_filename.c_str())));
+ continue;
+ }
+ if (args[argidx] == "-fst" && argidx+1 < args.size()) {
+ std::string fst_filename = args[++argidx];
+ rewrite_filename(fst_filename);
+ worker.outputfiles.emplace_back(std::unique_ptr<FSTWriter>(new FSTWriter(&worker, fst_filename.c_str())));
+ continue;
+ }
+ if (args[argidx] == "-aiw" && argidx+1 < args.size()) {
+ std::string aiw_filename = args[++argidx];
+ rewrite_filename(aiw_filename);
+ worker.outputfiles.emplace_back(std::unique_ptr<AIWWriter>(new AIWWriter(&worker, aiw_filename.c_str())));
continue;
}
if (args[argidx] == "-n" && argidx+1 < args.size()) {
numcycles = atoi(args[++argidx].c_str());
+ worker.cycles_set = true;
continue;
}
if (args[argidx] == "-rstlen" && argidx+1 < args.size()) {
@@ -848,6 +2080,10 @@ struct SimPass : public Pass {
worker.hide_internal = false;
continue;
}
+ if (args[argidx] == "-q") {
+ worker.verbose = false;
+ continue;
+ }
if (args[argidx] == "-d") {
worker.debug = true;
continue;
@@ -860,9 +2096,73 @@ struct SimPass : public Pass {
worker.zinit = true;
continue;
}
+ if (args[argidx] == "-r" && argidx+1 < args.size()) {
+ std::string sim_filename = args[++argidx];
+ rewrite_filename(sim_filename);
+ worker.sim_filename = sim_filename;
+ continue;
+ }
+ if (args[argidx] == "-map" && argidx+1 < args.size()) {
+ std::string map_filename = args[++argidx];
+ rewrite_filename(map_filename);
+ worker.map_filename = map_filename;
+ continue;
+ }
+ if (args[argidx] == "-scope" && argidx+1 < args.size()) {
+ worker.scope = args[++argidx];
+ continue;
+ }
+ if (args[argidx] == "-start" && argidx+1 < args.size()) {
+ worker.start_time = stringToTime(args[++argidx]);
+ start_set = true;
+ continue;
+ }
+ if (args[argidx] == "-stop" && argidx+1 < args.size()) {
+ worker.stop_time = stringToTime(args[++argidx]);
+ stop_set = true;
+ continue;
+ }
+ if (args[argidx] == "-at" && argidx+1 < args.size()) {
+ worker.start_time = stringToTime(args[++argidx]);
+ worker.stop_time = worker.start_time;
+ at_set = true;
+ continue;
+ }
+ if (args[argidx] == "-sim") {
+ worker.sim_mode = SimulationMode::sim;
+ continue;
+ }
+ if (args[argidx] == "-sim-cmp") {
+ worker.sim_mode = SimulationMode::cmp;
+ continue;
+ }
+ if (args[argidx] == "-sim-gold") {
+ worker.sim_mode = SimulationMode::gold;
+ continue;
+ }
+ if (args[argidx] == "-sim-gate") {
+ worker.sim_mode = SimulationMode::gate;
+ continue;
+ }
+ if (args[argidx] == "-x") {
+ worker.ignore_x = true;
+ continue;
+ }
+ if (args[argidx] == "-date") {
+ worker.date = true;
+ continue;
+ }
+ if (args[argidx] == "-multiclock") {
+ worker.multiclock = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
+ if (at_set && (start_set || stop_set || worker.cycles_set))
+ log_error("'at' option can only be defined separate of 'start','stop' and 'n'\n");
+ if (stop_set && worker.cycles_set)
+ log_error("'stop' and 'n' can only be used exclusively'\n");
Module *top_mod = nullptr;
@@ -878,8 +2178,139 @@ struct SimPass : public Pass {
top_mod = mods.front();
}
- worker.run(top_mod, numcycles);
+ if (worker.sim_filename.empty())
+ worker.run(top_mod, numcycles);
+ else {
+ std::string filename_trim = file_base_name(worker.sim_filename);
+ if (filename_trim.size() > 4 && ((filename_trim.compare(filename_trim.size()-4, std::string::npos, ".fst") == 0) ||
+ filename_trim.compare(filename_trim.size()-4, std::string::npos, ".vcd") == 0)) {
+ worker.run_cosim_fst(top_mod, numcycles);
+ } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".aiw") == 0) {
+ if (worker.map_filename.empty())
+ log_cmd_error("For AIGER witness file map parameter is mandatory.\n");
+ worker.run_cosim_aiger_witness(top_mod);
+ } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) {
+ worker.run_cosim_btor2_witness(top_mod);
+ } else {
+ log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str());
+ }
+ }
}
} SimPass;
+struct Fst2TbPass : public Pass {
+ Fst2TbPass() : Pass("fst2tb", "generate testbench out of fst file") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" fst2tb [options] [top-level]\n");
+ log("\n");
+ log("This command generates testbench for the circuit using the given top-level module\n");
+ log("and simulus signal from FST file\n");
+ log("\n");
+ log(" -tb <name>\n");
+ log(" generated testbench name.\n");
+ log(" files <name>.v and <name>.txt are created as result.\n");
+ log("\n");
+ log(" -r <filename>\n");
+ log(" read simulation FST file\n");
+ log("\n");
+ log(" -clock <portname>\n");
+ log(" name of top-level clock input\n");
+ log("\n");
+ log(" -clockn <portname>\n");
+ log(" name of top-level clock input (inverse polarity)\n");
+ log("\n");
+ log(" -scope <name>\n");
+ log(" scope of simulation top model\n");
+ log("\n");
+ log(" -start <time>\n");
+ log(" start co-simulation in arbitary time (default 0)\n");
+ log("\n");
+ log(" -stop <time>\n");
+ log(" stop co-simulation in arbitary time (default END)\n");
+ log("\n");
+ log(" -n <integer>\n");
+ log(" number of clock cycles to simulate (default: 20)\n");
+ log("\n");
+ }
+
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ SimWorker worker;
+ int numcycles = 20;
+ bool stop_set = false;
+ std::string tb_filename;
+
+ log_header(design, "Executing FST2FB pass.\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-clock" && argidx+1 < args.size()) {
+ worker.clock.insert(RTLIL::escape_id(args[++argidx]));
+ continue;
+ }
+ if (args[argidx] == "-clockn" && argidx+1 < args.size()) {
+ worker.clockn.insert(RTLIL::escape_id(args[++argidx]));
+ continue;
+ }
+ if (args[argidx] == "-r" && argidx+1 < args.size()) {
+ std::string sim_filename = args[++argidx];
+ rewrite_filename(sim_filename);
+ worker.sim_filename = sim_filename;
+ continue;
+ }
+ if (args[argidx] == "-n" && argidx+1 < args.size()) {
+ numcycles = atoi(args[++argidx].c_str());
+ worker.cycles_set = true;
+ continue;
+ }
+ if (args[argidx] == "-scope" && argidx+1 < args.size()) {
+ worker.scope = args[++argidx];
+ continue;
+ }
+ if (args[argidx] == "-start" && argidx+1 < args.size()) {
+ worker.start_time = stringToTime(args[++argidx]);
+ continue;
+ }
+ if (args[argidx] == "-stop" && argidx+1 < args.size()) {
+ worker.stop_time = stringToTime(args[++argidx]);
+ stop_set = true;
+ continue;
+ }
+ if (args[argidx] == "-tb" && argidx+1 < args.size()) {
+ tb_filename = args[++argidx];
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+ if (stop_set && worker.cycles_set)
+ log_error("'stop' and 'n' can only be used exclusively'\n");
+
+ Module *top_mod = nullptr;
+
+ if (design->full_selection()) {
+ top_mod = design->top_module();
+
+ if (!top_mod)
+ log_cmd_error("Design has no top module, use the 'hierarchy' command to specify one.\n");
+ } else {
+ auto mods = design->selected_whole_modules();
+ if (GetSize(mods) != 1)
+ log_cmd_error("Only one top module must be selected.\n");
+ top_mod = mods.front();
+ }
+
+ if (tb_filename.empty())
+ log_cmd_error("Testbench name must be defined.\n");
+
+ if (worker.sim_filename.empty())
+ log_cmd_error("Stimulus FST file must be defined.\n");
+
+ worker.generate_tb(top_mod, tb_filename, numcycles);
+ }
+} Fst2TbPass;
+
PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc
index 035699603..98ccfc303 100644
--- a/passes/techmap/Makefile.inc
+++ b/passes/techmap/Makefile.inc
@@ -29,6 +29,8 @@ OBJS += passes/techmap/extract_reduce.o
OBJS += passes/techmap/alumacc.o
OBJS += passes/techmap/dffinit.o
OBJS += passes/techmap/pmuxtree.o
+OBJS += passes/techmap/bmuxmap.o
+OBJS += passes/techmap/demuxmap.o
OBJS += passes/techmap/muxcover.o
OBJS += passes/techmap/aigmap.o
OBJS += passes/techmap/tribuf.o
diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc
index 80c6282c4..61ee99ee7 100644
--- a/passes/techmap/abc.cc
+++ b/passes/techmap/abc.cc
@@ -45,6 +45,7 @@
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/ffinit.h"
+#include "kernel/ff.h"
#include "kernel/cost.h"
#include "kernel/log.h"
#include <stdlib.h>
@@ -73,6 +74,8 @@ PRIVATE_NAMESPACE_BEGIN
enum class gate_type_t {
G_NONE,
G_FF,
+ G_FF0,
+ G_FF1,
G_BUF,
G_NOT,
G_AND,
@@ -112,13 +115,14 @@ int map_autoidx;
SigMap assign_map;
RTLIL::Module *module;
std::vector<gate_t> signal_list;
-std::map<RTLIL::SigBit, int> signal_map;
+dict<RTLIL::SigBit, int> signal_map;
FfInitVals initvals;
pool<std::string> enabled_gates;
-bool recover_init, cmos_cost;
+bool cmos_cost;
+bool had_init;
-bool clk_polarity, en_polarity;
-RTLIL::SigSpec clk_sig, en_sig;
+bool clk_polarity, en_polarity, arst_polarity, srst_polarity;
+RTLIL::SigSpec clk_sig, en_sig, arst_sig, srst_sig;
dict<int, std::string> pi_map, po_map;
int map_signal(RTLIL::SigBit bit, gate_type_t gate_type = G(NONE), int in1 = -1, int in2 = -1, int in3 = -1, int in4 = -1)
@@ -165,46 +169,84 @@ void mark_port(RTLIL::SigSpec sig)
void extract_cell(RTLIL::Cell *cell, bool keepff)
{
- if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_)))
- {
- if (clk_polarity != (cell->type == ID($_DFF_P_)))
+ if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
+ FfData ff(&initvals, cell);
+ gate_type_t type = G(FF);
+ if (!ff.has_clk)
return;
- if (clk_sig != assign_map(cell->getPort(ID::C)))
+ if (ff.has_gclk)
return;
- if (GetSize(en_sig) != 0)
+ if (ff.has_aload)
return;
- goto matching_dff;
- }
-
- if (cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
- {
- if (clk_polarity != cell->type.in(ID($_DFFE_PN_), ID($_DFFE_PP_)))
+ if (ff.has_sr)
return;
- if (en_polarity != cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_)))
+ if (!ff.is_fine)
return;
- if (clk_sig != assign_map(cell->getPort(ID::C)))
+ if (clk_polarity != ff.pol_clk)
return;
- if (en_sig != assign_map(cell->getPort(ID::E)))
+ if (clk_sig != assign_map(ff.sig_clk))
return;
- goto matching_dff;
- }
-
- if (0) {
- matching_dff:
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
+ if (ff.has_ce) {
+ if (en_polarity != ff.pol_ce)
+ return;
+ if (en_sig != assign_map(ff.sig_ce))
+ return;
+ } else {
+ if (GetSize(en_sig) != 0)
+ return;
+ }
+ if (ff.val_init == State::S1) {
+ type = G(FF1);
+ had_init = true;
+ } else if (ff.val_init == State::S0) {
+ type = G(FF0);
+ had_init = true;
+ }
+ if (ff.has_arst) {
+ if (arst_polarity != ff.pol_arst)
+ return;
+ if (arst_sig != assign_map(ff.sig_arst))
+ return;
+ if (ff.val_arst == State::S1) {
+ if (type == G(FF0))
+ return;
+ type = G(FF1);
+ } else if (ff.val_arst == State::S0) {
+ if (type == G(FF1))
+ return;
+ type = G(FF0);
+ }
+ } else {
+ if (GetSize(arst_sig) != 0)
+ return;
+ }
+ if (ff.has_srst) {
+ if (srst_polarity != ff.pol_srst)
+ return;
+ if (srst_sig != assign_map(ff.sig_srst))
+ return;
+ if (ff.val_srst == State::S1) {
+ if (type == G(FF0))
+ return;
+ type = G(FF1);
+ } else if (ff.val_srst == State::S0) {
+ if (type == G(FF1))
+ return;
+ type = G(FF0);
+ }
+ } else {
+ if (GetSize(srst_sig) != 0)
+ return;
+ }
if (keepff)
- for (auto &c : sig_q.chunks())
+ for (auto &c : ff.sig_q.chunks())
if (c.wire != nullptr)
c.wire->attributes[ID::keep] = 1;
- assign_map.apply(sig_d);
- assign_map.apply(sig_q);
-
- map_signal(sig_q, G(FF), map_signal(sig_d));
+ map_signal(ff.sig_q, type, map_signal(ff.sig_d));
- module->remove(cell);
+ ff.remove();
return;
}
@@ -367,7 +409,7 @@ std::string remap_name(RTLIL::IdString abc_name, RTLIL::Wire **orig_wire = nullp
return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1);
}
-void dump_loop_graph(FILE *f, int &nr, std::map<int, std::set<int>> &edges, std::set<int> &workpool, std::vector<int> &in_counts)
+void dump_loop_graph(FILE *f, int &nr, dict<int, pool<int>> &edges, pool<int> &workpool, std::vector<int> &in_counts)
{
if (f == nullptr)
return;
@@ -378,7 +420,7 @@ void dump_loop_graph(FILE *f, int &nr, std::map<int, std::set<int>> &edges, std:
fprintf(f, " label=\"slide%d\";\n", nr);
fprintf(f, " rankdir=\"TD\";\n");
- std::set<int> nodes;
+ pool<int> nodes;
for (auto &e : edges) {
nodes.insert(e.first);
for (auto n : e.second)
@@ -401,9 +443,9 @@ void handle_loops()
// http://en.wikipedia.org/wiki/Topological_sorting
// (Kahn, Arthur B. (1962), "Topological sorting of large networks")
- std::map<int, std::set<int>> edges;
+ dict<int, pool<int>> edges;
std::vector<int> in_edges_count(signal_list.size());
- std::set<int> workpool;
+ pool<int> workpool;
FILE *dot_f = nullptr;
int dot_nr = 0;
@@ -412,7 +454,7 @@ void handle_loops()
// dot_f = fopen("test.dot", "w");
for (auto &g : signal_list) {
- if (g.type == G(NONE) || g.type == G(FF)) {
+ if (g.type == G(NONE) || g.type == G(FF) || g.type == G(FF0) || g.type == G(FF1)) {
workpool.insert(g.id);
} else {
if (g.in1 >= 0) {
@@ -667,7 +709,6 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
signal_list.clear();
pi_map.clear();
po_map.clear();
- recover_init = false;
if (clk_str != "$")
{
@@ -676,14 +717,41 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
en_polarity = true;
en_sig = RTLIL::SigSpec();
+
+ arst_polarity = true;
+ arst_sig = RTLIL::SigSpec();
+
+ srst_polarity = true;
+ srst_sig = RTLIL::SigSpec();
}
if (!clk_str.empty() && clk_str != "$")
{
+ std::string en_str;
+ std::string arst_str;
+ std::string srst_str;
if (clk_str.find(',') != std::string::npos) {
int pos = clk_str.find(',');
- std::string en_str = clk_str.substr(pos+1);
+ en_str = clk_str.substr(pos+1);
clk_str = clk_str.substr(0, pos);
+ }
+ if (en_str.find(',') != std::string::npos) {
+ int pos = en_str.find(',');
+ arst_str = en_str.substr(pos+1);
+ arst_str = en_str.substr(0, pos);
+ }
+ if (arst_str.find(',') != std::string::npos) {
+ int pos = arst_str.find(',');
+ srst_str = arst_str.substr(pos+1);
+ srst_str = arst_str.substr(0, pos);
+ }
+ if (clk_str[0] == '!') {
+ clk_polarity = false;
+ clk_str = clk_str.substr(1);
+ }
+ if (module->wire(RTLIL::escape_id(clk_str)) != nullptr)
+ clk_sig = assign_map(module->wire(RTLIL::escape_id(clk_str)));
+ if (en_str != "") {
if (en_str[0] == '!') {
en_polarity = false;
en_str = en_str.substr(1);
@@ -691,18 +759,28 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
if (module->wire(RTLIL::escape_id(en_str)) != nullptr)
en_sig = assign_map(module->wire(RTLIL::escape_id(en_str)));
}
- if (clk_str[0] == '!') {
- clk_polarity = false;
- clk_str = clk_str.substr(1);
+ if (arst_str != "") {
+ if (arst_str[0] == '!') {
+ arst_polarity = false;
+ arst_str = arst_str.substr(1);
+ }
+ if (module->wire(RTLIL::escape_id(arst_str)) != nullptr)
+ arst_sig = assign_map(module->wire(RTLIL::escape_id(arst_str)));
+ }
+ if (srst_str != "") {
+ if (srst_str[0] == '!') {
+ srst_polarity = false;
+ srst_str = srst_str.substr(1);
+ }
+ if (module->wire(RTLIL::escape_id(srst_str)) != nullptr)
+ srst_sig = assign_map(module->wire(RTLIL::escape_id(srst_str)));
}
- if (module->wire(RTLIL::escape_id(clk_str)) != nullptr)
- clk_sig = assign_map(module->wire(RTLIL::escape_id(clk_str)));
}
if (dff_mode && clk_sig.empty())
log_cmd_error("Clock domain %s not found.\n", clk_str.c_str());
- std::string tempdir_name = "/tmp/" + proc_program_prefix()+ "yosys-abc-XXXXXX";
+ std::string tempdir_name = get_base_tmpdir() + "/" + proc_program_prefix()+ "yosys-abc-XXXXXX";
if (!cleanup)
tempdir_name[0] = tempdir_name[4] = '_';
tempdir_name = make_temp_dir(tempdir_name);
@@ -757,10 +835,10 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
for (size_t pos = abc_script.find("{D}"); pos != std::string::npos; pos = abc_script.find("{D}", pos))
abc_script = abc_script.substr(0, pos) + delay_target + abc_script.substr(pos+3);
- for (size_t pos = abc_script.find("{I}"); pos != std::string::npos; pos = abc_script.find("{D}", pos))
+ for (size_t pos = abc_script.find("{I}"); pos != std::string::npos; pos = abc_script.find("{I}", pos))
abc_script = abc_script.substr(0, pos) + sop_inputs + abc_script.substr(pos+3);
- for (size_t pos = abc_script.find("{P}"); pos != std::string::npos; pos = abc_script.find("{D}", pos))
+ for (size_t pos = abc_script.find("{P}"); pos != std::string::npos; pos = abc_script.find("{P}", pos))
abc_script = abc_script.substr(0, pos) + sop_products + abc_script.substr(pos+3);
for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos))
@@ -789,10 +867,15 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
log("Found%s %s clock domain: %s", clk_str.empty() ? "" : " matching", clk_polarity ? "posedge" : "negedge", log_signal(clk_sig));
if (en_sig.size() != 0)
log(", enabled by %s%s", en_polarity ? "" : "!", log_signal(en_sig));
+ if (arst_sig.size() != 0)
+ log(", asynchronously reset by %s%s", arst_polarity ? "" : "!", log_signal(arst_sig));
+ if (srst_sig.size() != 0)
+ log(", synchronously reset by %s%s", srst_polarity ? "" : "!", log_signal(srst_sig));
log("\n");
}
}
+ had_init = false;
for (auto c : cells)
extract_cell(c, keepff);
@@ -811,6 +894,12 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
if (en_sig.size() != 0)
mark_port(en_sig);
+ if (arst_sig.size() != 0)
+ mark_port(arst_sig);
+
+ if (srst_sig.size() != 0)
+ mark_port(srst_sig);
+
handle_loops();
buffer = stringf("%s/input.blif", tempdir_name.c_str());
@@ -917,11 +1006,11 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
fprintf(f, "00-- 1\n");
fprintf(f, "--00 1\n");
} else if (si.type == G(FF)) {
- if (si.init == State::S0 || si.init == State::S1) {
- fprintf(f, ".latch ys__n%d ys__n%d %d\n", si.in1, si.id, si.init == State::S1 ? 1 : 0);
- recover_init = true;
- } else
- fprintf(f, ".latch ys__n%d ys__n%d 2\n", si.in1, si.id);
+ fprintf(f, ".latch ys__n%d ys__n%d 2\n", si.in1, si.id);
+ } else if (si.type == G(FF0)) {
+ fprintf(f, ".latch ys__n%d ys__n%d 0\n", si.in1, si.id);
+ } else if (si.type == G(FF1)) {
+ fprintf(f, ".latch ys__n%d ys__n%d 1\n", si.in1, si.id);
} else if (si.type != G(NONE))
log_abort();
if (si.type != G(NONE))
@@ -1043,7 +1132,10 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
design->select(module, wire);
}
- std::map<std::string, int> cell_stats;
+ SigMap mapped_sigmap(mapped_mod);
+ FfInitVals mapped_initvals(&mapped_sigmap, mapped_mod);
+
+ dict<std::string, int> cell_stats;
for (auto c : mapped_mod->cells())
{
if (builtin_lib)
@@ -1149,20 +1241,41 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
}
if (c->type == ID(DFF)) {
log_assert(clk_sig.size() == 1);
- RTLIL::Cell *cell;
- if (en_sig.size() == 0) {
- cell = module->addCell(remap_name(c->name), clk_polarity ? ID($_DFF_P_) : ID($_DFF_N_));
- } else {
+ FfData ff(module, &initvals, remap_name(c->name));
+ ff.width = 1;
+ ff.is_fine = true;
+ ff.has_clk = true;
+ ff.pol_clk = clk_polarity;
+ ff.sig_clk = clk_sig;
+ if (en_sig.size() != 0) {
log_assert(en_sig.size() == 1);
- cell = module->addCell(remap_name(c->name), stringf("$_DFFE_%c%c_", clk_polarity ? 'P' : 'N', en_polarity ? 'P' : 'N'));
- cell->setPort(ID::E, en_sig);
+ ff.has_ce = true;
+ ff.pol_ce = en_polarity;
+ ff.sig_ce = en_sig;
}
- if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx;
- for (auto name : {ID::D, ID::Q}) {
- RTLIL::IdString remapped_name = remap_name(c->getPort(name).as_wire()->name);
- cell->setPort(name, module->wire(remapped_name));
+ RTLIL::Const init = mapped_initvals(c->getPort(ID::Q));
+ if (had_init)
+ ff.val_init = init;
+ else
+ ff.val_init = State::Sx;
+ if (arst_sig.size() != 0) {
+ log_assert(arst_sig.size() == 1);
+ ff.has_arst = true;
+ ff.pol_arst = arst_polarity;
+ ff.sig_arst = arst_sig;
+ ff.val_arst = init;
}
- cell->setPort(ID::C, clk_sig);
+ if (srst_sig.size() != 0) {
+ log_assert(srst_sig.size() == 1);
+ ff.has_srst = true;
+ ff.pol_srst = srst_polarity;
+ ff.sig_srst = srst_sig;
+ ff.val_srst = init;
+ }
+ ff.sig_d = module->wire(remap_name(c->getPort(ID::D).as_wire()->name));
+ ff.sig_q = module->wire(remap_name(c->getPort(ID::Q).as_wire()->name));
+ RTLIL::Cell *cell = ff.emit();
+ if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx;
design->select(module, cell);
continue;
}
@@ -1180,20 +1293,38 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
if (c->type == ID(_dff_)) {
log_assert(clk_sig.size() == 1);
- RTLIL::Cell *cell;
- if (en_sig.size() == 0) {
- cell = module->addCell(remap_name(c->name), clk_polarity ? ID($_DFF_P_) : ID($_DFF_N_));
- } else {
+ FfData ff(module, &initvals, remap_name(c->name));
+ ff.width = 1;
+ ff.is_fine = true;
+ ff.has_clk = true;
+ ff.pol_clk = clk_polarity;
+ ff.sig_clk = clk_sig;
+ if (en_sig.size() != 0) {
log_assert(en_sig.size() == 1);
- cell = module->addCell(remap_name(c->name), stringf("$_DFFE_%c%c_", clk_polarity ? 'P' : 'N', en_polarity ? 'P' : 'N'));
- cell->setPort(ID::E, en_sig);
+ ff.pol_ce = en_polarity;
+ ff.sig_ce = en_sig;
}
- if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx;
- for (auto name : {ID::D, ID::Q}) {
- RTLIL::IdString remapped_name = remap_name(c->getPort(name).as_wire()->name);
- cell->setPort(name, module->wire(remapped_name));
+ RTLIL::Const init = mapped_initvals(c->getPort(ID::Q));
+ if (had_init)
+ ff.val_init = init;
+ else
+ ff.val_init = State::Sx;
+ if (arst_sig.size() != 0) {
+ log_assert(arst_sig.size() == 1);
+ ff.pol_arst = arst_polarity;
+ ff.sig_arst = arst_sig;
+ ff.val_arst = init;
+ }
+ if (srst_sig.size() != 0) {
+ log_assert(srst_sig.size() == 1);
+ ff.pol_srst = srst_polarity;
+ ff.sig_srst = srst_sig;
+ ff.val_srst = init;
}
- cell->setPort(ID::C, clk_sig);
+ ff.sig_d = module->wire(remap_name(c->getPort(ID::D).as_wire()->name));
+ ff.sig_q = module->wire(remap_name(c->getPort(ID::Q).as_wire()->name));
+ RTLIL::Cell *cell = ff.emit();
+ if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx;
design->select(module, cell);
continue;
}
@@ -1229,15 +1360,6 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
module->connect(conn);
}
- if (recover_init)
- for (auto wire : mapped_mod->wires()) {
- if (wire->attributes.count(ID::init)) {
- Wire *w = module->wire(remap_name(wire->name));
- log_assert(w->attributes.count(ID::init) == 0);
- w->attributes[ID::init] = wire->attributes.at(ID::init);
- }
- }
-
for (auto &it : cell_stats)
log("ABC RESULTS: %15s cells: %8d\n", it.first.c_str(), it.second);
int in_wires = 0, out_wires = 0;
@@ -1878,18 +2000,18 @@ struct AbcPass : public Pass {
CellTypes ct(design);
std::vector<RTLIL::Cell*> all_cells = mod->selected_cells();
- std::set<RTLIL::Cell*> unassigned_cells(all_cells.begin(), all_cells.end());
+ pool<RTLIL::Cell*> unassigned_cells(all_cells.begin(), all_cells.end());
- std::set<RTLIL::Cell*> expand_queue, next_expand_queue;
- std::set<RTLIL::Cell*> expand_queue_up, next_expand_queue_up;
- std::set<RTLIL::Cell*> expand_queue_down, next_expand_queue_down;
+ pool<RTLIL::Cell*> expand_queue, next_expand_queue;
+ pool<RTLIL::Cell*> expand_queue_up, next_expand_queue_up;
+ pool<RTLIL::Cell*> expand_queue_down, next_expand_queue_down;
- typedef tuple<bool, RTLIL::SigSpec, bool, RTLIL::SigSpec> clkdomain_t;
- std::map<clkdomain_t, std::vector<RTLIL::Cell*>> assigned_cells;
- std::map<RTLIL::Cell*, clkdomain_t> assigned_cells_reverse;
+ typedef tuple<bool, RTLIL::SigSpec, bool, RTLIL::SigSpec, bool, RTLIL::SigSpec, bool, RTLIL::SigSpec> clkdomain_t;
+ dict<clkdomain_t, std::vector<RTLIL::Cell*>> assigned_cells;
+ dict<RTLIL::Cell*, clkdomain_t> assigned_cells_reverse;
- std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_bit, cell_to_bit_up, cell_to_bit_down;
- std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> bit_to_cell, bit_to_cell_up, bit_to_cell_down;
+ dict<RTLIL::Cell*, pool<RTLIL::SigBit>> cell_to_bit, cell_to_bit_up, cell_to_bit_down;
+ dict<RTLIL::SigBit, pool<RTLIL::Cell*>> bit_to_cell, bit_to_cell_up, bit_to_cell_down;
for (auto cell : all_cells)
{
@@ -1912,19 +2034,30 @@ struct AbcPass : public Pass {
}
}
- if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_)))
- {
- key = clkdomain_t(cell->type == ID($_DFF_P_), assign_map(cell->getPort(ID::C)), true, RTLIL::SigSpec());
- }
- else
- if (cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
- {
- bool this_clk_pol = cell->type.in(ID($_DFFE_PN_), ID($_DFFE_PP_));
- bool this_en_pol = cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_));
- key = clkdomain_t(this_clk_pol, assign_map(cell->getPort(ID::C)), this_en_pol, assign_map(cell->getPort(ID::E)));
- }
- else
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type))
+ continue;
+
+ FfData ff(&initvals, cell);
+ if (!ff.has_clk)
+ continue;
+ if (ff.has_gclk)
+ continue;
+ if (ff.has_aload)
+ continue;
+ if (ff.has_sr)
+ continue;
+ if (!ff.is_fine)
continue;
+ key = clkdomain_t(
+ ff.pol_clk,
+ ff.sig_clk,
+ ff.has_ce ? ff.pol_ce : true,
+ ff.has_ce ? assign_map(ff.sig_ce) : RTLIL::SigSpec(),
+ ff.has_arst ? ff.pol_arst : true,
+ ff.has_arst ? assign_map(ff.sig_arst) : RTLIL::SigSpec(),
+ ff.has_srst ? ff.pol_srst : true,
+ ff.has_srst ? assign_map(ff.sig_srst) : RTLIL::SigSpec()
+ );
unassigned_cells.erase(cell);
expand_queue.insert(cell);
@@ -1998,7 +2131,7 @@ struct AbcPass : public Pass {
expand_queue.swap(next_expand_queue);
}
- clkdomain_t key(true, RTLIL::SigSpec(), true, RTLIL::SigSpec());
+ clkdomain_t key(true, RTLIL::SigSpec(), true, RTLIL::SigSpec(), true, RTLIL::SigSpec(), true, RTLIL::SigSpec());
for (auto cell : unassigned_cells) {
assigned_cells[key].push_back(cell);
assigned_cells_reverse[cell] = key;
@@ -2006,15 +2139,21 @@ struct AbcPass : public Pass {
log_header(design, "Summary of detected clock domains:\n");
for (auto &it : assigned_cells)
- log(" %d cells in clk=%s%s, en=%s%s\n", GetSize(it.second),
+ log(" %d cells in clk=%s%s, en=%s%s, arst=%s%s, srst=%s%s\n", GetSize(it.second),
std::get<0>(it.first) ? "" : "!", log_signal(std::get<1>(it.first)),
- std::get<2>(it.first) ? "" : "!", log_signal(std::get<3>(it.first)));
+ std::get<2>(it.first) ? "" : "!", log_signal(std::get<3>(it.first)),
+ std::get<4>(it.first) ? "" : "!", log_signal(std::get<5>(it.first)),
+ std::get<6>(it.first) ? "" : "!", log_signal(std::get<7>(it.first)));
for (auto &it : assigned_cells) {
clk_polarity = std::get<0>(it.first);
clk_sig = assign_map(std::get<1>(it.first));
en_polarity = std::get<2>(it.first);
en_sig = assign_map(std::get<3>(it.first));
+ arst_polarity = std::get<4>(it.first);
+ arst_sig = assign_map(std::get<5>(it.first));
+ srst_polarity = std::get<6>(it.first);
+ srst_sig = assign_map(std::get<7>(it.first));
abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$",
keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode, abc_dress);
assign_map.set(mod);
diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc
index 1f00fc3e7..79c994b11 100644
--- a/passes/techmap/abc9.cc
+++ b/passes/techmap/abc9.cc
@@ -74,12 +74,18 @@ struct Abc9Pass : public ScriptPass
/* Comm3 */ "&synch2 -K 6 -C 500; &if -m "/*"-E 5"*/" {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\
/* Comm2 */ "&dch -C 500; &if -m {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save; "\
"&load";
- // Based on ABC's &flow3
+ // Based on ABC's &flow3 -m
RTLIL::constpad["abc9.script.flow3"] = "+&scorr; &sweep;" \
"&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\
"&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &save; &load;"\
"&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &save; &load;"\
"&mfs";
+ // As above, but with &mfs calls as in the original &flow3
+ RTLIL::constpad["abc9.script.flow3mfs"] = "+&scorr; &sweep;" \
+ "&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\
+ "&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\
+ "&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\
+ "&mfs";
}
void help() override
{
@@ -398,7 +404,7 @@ struct Abc9Pass : public ScriptPass
if (!active_design->selected_whole_module(mod))
log_error("Can't handle partially selected module %s!\n", log_id(mod));
- std::string tempdir_name = "/tmp/" + proc_program_prefix() + "yosys-abc-XXXXXX";
+ std::string tempdir_name = get_base_tmpdir() + "/" + proc_program_prefix() + "yosys-abc-XXXXXX";
if (!cleanup)
tempdir_name[0] = tempdir_name[4] = '_';
tempdir_name = make_temp_dir(tempdir_name);
diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc
index 29fe74ec7..acafb0b65 100644
--- a/passes/techmap/abc9_ops.cc
+++ b/passes/techmap/abc9_ops.cc
@@ -155,6 +155,9 @@ void prep_hier(RTLIL::Design *design, bool dff_mode)
r.first->second = new Design;
Design *unmap_design = r.first->second;
+ // Keep track of derived versions of modules that we haven't used, to prevent these being used for unwanted techmaps later on.
+ pool<IdString> unused_derived;
+
for (auto module : design->selected_modules())
for (auto cell : module->cells()) {
auto inst_module = design->module(cell->type);
@@ -167,12 +170,9 @@ void prep_hier(RTLIL::Design *design, bool dff_mode)
derived_module = inst_module;
}
else {
- // Check potential for any one of those three
- // (since its value may depend on a parameter, but not its existence)
- if (!inst_module->has_attribute(ID::abc9_flop) && !inst_module->has_attribute(ID::abc9_box) && !inst_module->get_bool_attribute(ID::abc9_bypass))
- continue;
derived_type = inst_module->derive(design, cell->parameters);
derived_module = design->module(derived_type);
+ unused_derived.insert(derived_type);
}
if (derived_module->get_bool_attribute(ID::abc9_flop)) {
@@ -180,13 +180,23 @@ void prep_hier(RTLIL::Design *design, bool dff_mode)
continue;
}
else {
- if (!derived_module->get_bool_attribute(ID::abc9_box) && !derived_module->get_bool_attribute(ID::abc9_bypass)) {
+ bool has_timing = false;
+ for (auto derived_cell : derived_module->cells()) {
+ if (derived_cell->type.in(ID($specify2), ID($specify3), ID($specrule))) {
+ // If the module contains timing; then we potentially care about deriving its content too,
+ // as timings (or associated port widths) could be dependent on parameters.
+ has_timing = true;
+ break;
+ }
+ }
+ if (!derived_module->get_bool_attribute(ID::abc9_box) && !derived_module->get_bool_attribute(ID::abc9_bypass) && !has_timing) {
if (unmap_design->module(derived_type)) {
// If derived_type is present in unmap_design, it means that it was processed previously, but found to be incompatible -- e.g. if
// it contained a non-zero initial state. In this case, continue to replace the cell type/parameters so that it has the same properties
// as a compatible type, yet will be safely unmapped later
cell->type = derived_type;
cell->parameters.clear();
+ unused_derived.erase(derived_type);
}
continue;
}
@@ -245,7 +255,11 @@ void prep_hier(RTLIL::Design *design, bool dff_mode)
cell->type = derived_type;
cell->parameters.clear();
+ unused_derived.erase(derived_type);
}
+ for (auto unused : unused_derived) {
+ design->remove(design->module(unused));
+ }
}
void prep_bypass(RTLIL::Design *design)
diff --git a/passes/techmap/bmuxmap.cc b/passes/techmap/bmuxmap.cc
new file mode 100644
index 000000000..03673c278
--- /dev/null
+++ b/passes/techmap/bmuxmap.cc
@@ -0,0 +1,76 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2022 Marcelina Koƛcielnicka <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
+
+struct BmuxmapPass : public Pass {
+ BmuxmapPass() : Pass("bmuxmap", "transform $bmux cells to trees of $mux cells") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" bmuxmap [selection]\n");
+ log("\n");
+ log("This pass transforms $bmux cells to trees of $mux cells.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ log_header(design, "Executing BMUXMAP pass.\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules())
+ for (auto cell : module->selected_cells())
+ {
+ if (cell->type != ID($bmux))
+ continue;
+
+ SigSpec sel = cell->getPort(ID::S);
+ SigSpec data = cell->getPort(ID::A);
+ int width = GetSize(cell->getPort(ID::Y));
+
+ for (int idx = 0; idx < GetSize(sel); idx++) {
+ SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2);
+ for (int i = 0; i < GetSize(new_data); i += width) {
+ RTLIL::Cell *mux = module->addMux(NEW_ID,
+ data.extract(i*2, width),
+ data.extract(i*2+width, width),
+ sel[idx],
+ new_data.extract(i, width));
+ mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+ }
+ data = new_data;
+ }
+
+ module->connect(cell->getPort(ID::Y), data);
+ module->remove(cell);
+ }
+ }
+} BmuxmapPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/demuxmap.cc b/passes/techmap/demuxmap.cc
new file mode 100644
index 000000000..292b18bad
--- /dev/null
+++ b/passes/techmap/demuxmap.cc
@@ -0,0 +1,80 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2022 Marcelina Koƛcielnicka <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
+
+struct DemuxmapPass : public Pass {
+ DemuxmapPass() : Pass("demuxmap", "transform $demux cells to $eq + $mux cells") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" demuxmap [selection]\n");
+ log("\n");
+ log("This pass transforms $demux cells to a bunch of equality comparisons.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ log_header(design, "Executing DEMUXMAP pass.\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ for (auto module : design->selected_modules())
+ for (auto cell : module->selected_cells())
+ {
+ if (cell->type != ID($demux))
+ continue;
+
+ SigSpec sel = cell->getPort(ID::S);
+ SigSpec data = cell->getPort(ID::A);
+ SigSpec out = cell->getPort(ID::Y);
+ int width = GetSize(cell->getPort(ID::A));
+
+ for (int i = 0; i < 1 << GetSize(sel); i++) {
+ if (width == 1 && data == State::S1) {
+ RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), out[i]);
+ eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+ } else {
+ Wire *eq = module->addWire(NEW_ID);
+ RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), eq);
+ eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+ RTLIL::Cell *mux = module->addMux(NEW_ID,
+ Const(State::S0, width),
+ data,
+ eq,
+ out.extract(i*width, width));
+ mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+ }
+ }
+
+ module->remove(cell);
+ }
+ }
+} DemuxmapPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/techmap/iopadmap.cc b/passes/techmap/iopadmap.cc
index 990e28876..322eb7779 100644
--- a/passes/techmap/iopadmap.cc
+++ b/passes/techmap/iopadmap.cc
@@ -46,7 +46,7 @@ struct IopadmapPass : public Pass {
log(" -inpad <celltype> <in_port>[:<ext_port>]\n");
log(" Map module input ports to the given cell type with the\n");
log(" given output port name. if a 2nd portname is given, the\n");
- log(" signal is passed through the pad call, using the 2nd\n");
+ log(" signal is passed through the pad cell, using the 2nd\n");
log(" portname as the port facing the module port.\n");
log("\n");
log(" -outpad <celltype> <out_port>[:<ext_port>]\n");
@@ -240,13 +240,13 @@ struct IopadmapPass : public Pass {
for (auto module : design->selected_modules())
{
dict<Wire *, dict<int, pair<Cell *, IdString>>> rewrite_bits;
- pool<SigSig> remove_conns;
+ dict<SigSig, pool<int>> remove_conns;
if (!toutpad_celltype.empty() || !tinoutpad_celltype.empty())
{
dict<SigBit, Cell *> tbuf_bits;
pool<SigBit> driven_bits;
- dict<SigBit, SigSig> z_conns;
+ dict<SigBit, std::pair<SigSig, int>> z_conns;
// Gather tristate buffers and always-on drivers.
for (auto cell : module->cells())
@@ -266,7 +266,7 @@ struct IopadmapPass : public Pass {
SigBit dstbit = conn.first[i];
SigBit srcbit = conn.second[i];
if (!srcbit.wire && srcbit.data == State::Sz) {
- z_conns[dstbit] = conn;
+ z_conns[dstbit] = {conn, i};
continue;
}
driven_bits.insert(dstbit);
@@ -317,8 +317,9 @@ struct IopadmapPass : public Pass {
// enable.
en_sig = SigBit(State::S0);
data_sig = SigBit(State::Sx);
- if (z_conns.count(wire_bit))
- remove_conns.insert(z_conns[wire_bit]);
+ auto it = z_conns.find(wire_bit);
+ if (it != z_conns.end())
+ remove_conns[it->second.first].insert(it->second.second);
}
if (wire->port_input)
@@ -477,9 +478,22 @@ struct IopadmapPass : public Pass {
if (!remove_conns.empty()) {
std::vector<SigSig> new_conns;
- for (auto &conn : module->connections())
- if (!remove_conns.count(conn))
+ for (auto &conn : module->connections()) {
+ auto it = remove_conns.find(conn);
+ if (it == remove_conns.end()) {
new_conns.push_back(conn);
+ } else {
+ SigSpec lhs, rhs;
+ for (int i = 0; i < GetSize(conn.first); i++) {
+ if (!it->second.count(i)) {
+ lhs.append(conn.first[i]);
+ rhs.append(conn.second[i]);
+ }
+ }
+ new_conns.push_back(SigSig(lhs, rhs));
+
+ }
+ }
module->new_connections(new_conns);
}
diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc
index 68f44cf6d..7d8dba439 100644
--- a/passes/techmap/simplemap.cc
+++ b/passes/techmap/simplemap.cc
@@ -299,6 +299,30 @@ void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell)
}
}
+void simplemap_bmux(RTLIL::Module *module, RTLIL::Cell *cell)
+{
+ SigSpec sel = cell->getPort(ID::S);
+ SigSpec data = cell->getPort(ID::A);
+ int width = GetSize(cell->getPort(ID::Y));
+
+ for (int idx = 0; idx < GetSize(sel); idx++) {
+ SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2);
+ for (int i = 0; i < GetSize(new_data); i += width) {
+ for (int k = 0; k < width; k++) {
+ RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_));
+ gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+ gate->setPort(ID::A, data[i*2+k]);
+ gate->setPort(ID::B, data[i*2+width+k]);
+ gate->setPort(ID::S, sel[idx]);
+ gate->setPort(ID::Y, new_data[i+k]);
+ }
+ }
+ data = new_data;
+ }
+
+ module->connect(cell->getPort(ID::Y), data);
+}
+
void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell)
{
SigSpec lut_ctrl = cell->getPort(ID::A);
@@ -306,7 +330,6 @@ void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell)
lut_data.extend_u0(1 << cell->getParam(ID::WIDTH).as_int());
for (int idx = 0; GetSize(lut_data) > 1; idx++) {
- SigSpec sig_s = lut_ctrl[idx];
SigSpec new_lut_data = module->addWire(NEW_ID, GetSize(lut_data)/2);
for (int i = 0; i < GetSize(lut_data); i += 2) {
RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_));
@@ -400,6 +423,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)>
mappers[ID($nex)] = simplemap_eqne;
mappers[ID($mux)] = simplemap_mux;
mappers[ID($tribuf)] = simplemap_tribuf;
+ mappers[ID($bmux)] = simplemap_bmux;
mappers[ID($lut)] = simplemap_lut;
mappers[ID($sop)] = simplemap_sop;
mappers[ID($slice)] = simplemap_slice;
diff --git a/passes/techmap/tribuf.cc b/passes/techmap/tribuf.cc
index f92b4cdb0..b45cd268a 100644
--- a/passes/techmap/tribuf.cc
+++ b/passes/techmap/tribuf.cc
@@ -26,10 +26,12 @@ PRIVATE_NAMESPACE_BEGIN
struct TribufConfig {
bool merge_mode;
bool logic_mode;
+ bool formal_mode;
TribufConfig() {
merge_mode = false;
logic_mode = false;
+ formal_mode = false;
}
};
@@ -55,7 +57,7 @@ struct TribufWorker {
dict<SigSpec, vector<Cell*>> tribuf_cells;
pool<SigBit> output_bits;
- if (config.logic_mode)
+ if (config.logic_mode || config.formal_mode)
for (auto wire : module->wires())
if (wire->port_output)
for (auto bit : sigmap(wire))
@@ -102,22 +104,54 @@ struct TribufWorker {
}
}
- if (config.merge_mode || config.logic_mode)
+ if (config.merge_mode || config.logic_mode || config.formal_mode)
{
for (auto &it : tribuf_cells)
{
bool no_tribuf = false;
- if (config.logic_mode) {
+ if (config.logic_mode && !config.formal_mode) {
no_tribuf = true;
for (auto bit : it.first)
if (output_bits.count(bit))
no_tribuf = false;
}
+ if (config.formal_mode)
+ no_tribuf = true;
+
if (GetSize(it.second) <= 1 && !no_tribuf)
continue;
+ if (config.formal_mode && GetSize(it.second) >= 2) {
+ for (auto cell : it.second) {
+ SigSpec others_s;
+
+ for (auto other_cell : it.second) {
+ if (other_cell == cell)
+ continue;
+ else if (other_cell->type == ID($tribuf))
+ others_s.append(other_cell->getPort(ID::EN));
+ else
+ others_s.append(other_cell->getPort(ID::E));
+ }
+
+ auto cell_s = cell->type == ID($tribuf) ? cell->getPort(ID::EN) : cell->getPort(ID::E);
+
+ auto other_s = module->ReduceOr(NEW_ID, others_s);
+
+ auto conflict = module->And(NEW_ID, cell_s, other_s);
+
+ std::string name = stringf("$tribuf_conflict$%s", log_id(cell->name));
+ auto assert_cell = module->addAssert(name, module->Not(NEW_ID, conflict), SigSpec(true));
+
+ assert_cell->set_src_attribute(cell->get_src_attribute());
+ assert_cell->set_bool_attribute(ID::keep);
+
+ module->design->scratchpad_set_bool("tribuf.added_something", true);
+ }
+ }
+
SigSpec pmux_b, pmux_s;
for (auto cell : it.second) {
if (cell->type == ID($tribuf))
@@ -159,6 +193,11 @@ struct TribufPass : public Pass {
log(" convert tri-state buffers that do not drive output ports\n");
log(" to non-tristate logic. this option implies -merge.\n");
log("\n");
+ log(" -formal\n");
+ log(" convert all tri-state buffers to non-tristate logic and\n");
+ log(" add a formal assertion that no two buffers are driving the\n");
+ log(" same net simultaneously. this option implies -merge.\n");
+ log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
@@ -176,6 +215,10 @@ struct TribufPass : public Pass {
config.logic_mode = true;
continue;
}
+ if (args[argidx] == "-formal") {
+ config.formal_mode = true;
+ continue;
+ }
break;
}
extra_args(args, argidx, design);
diff --git a/passes/tests/test_cell.cc b/passes/tests/test_cell.cc
index 4e437e409..e21ec452c 100644
--- a/passes/tests/test_cell.cc
+++ b/passes/tests/test_cell.cc
@@ -69,6 +69,48 @@ static void create_gold_module(RTLIL::Design *design, RTLIL::IdString cell_type,
cell->setPort(ID::Y, wire);
}
+ if (cell_type == ID($bmux))
+ {
+ int width = 1 + xorshift32(8);
+ int swidth = 1 + xorshift32(4);
+
+ wire = module->addWire(ID::A);
+ wire->width = width << swidth;
+ wire->port_input = true;
+ cell->setPort(ID::A, wire);
+
+ wire = module->addWire(ID::S);
+ wire->width = swidth;
+ wire->port_input = true;
+ cell->setPort(ID::S, wire);
+
+ wire = module->addWire(ID::Y);
+ wire->width = width;
+ wire->port_output = true;
+ cell->setPort(ID::Y, wire);
+ }
+
+ if (cell_type == ID($demux))
+ {
+ int width = 1 + xorshift32(8);
+ int swidth = 1 + xorshift32(6);
+
+ wire = module->addWire(ID::A);
+ wire->width = width;
+ wire->port_input = true;
+ cell->setPort(ID::A, wire);
+
+ wire = module->addWire(ID::S);
+ wire->width = swidth;
+ wire->port_input = true;
+ cell->setPort(ID::S, wire);
+
+ wire = module->addWire(ID::Y);
+ wire->width = width << swidth;
+ wire->port_output = true;
+ cell->setPort(ID::Y, wire);
+ }
+
if (cell_type == ID($fa))
{
int width = 1 + xorshift32(8);
@@ -855,8 +897,10 @@ struct TestCellPass : public Pass {
cell_types[ID($logic_and)] = "ABSY";
cell_types[ID($logic_or)] = "ABSY";
+ cell_types[ID($mux)] = "*";
+ cell_types[ID($bmux)] = "*";
+ cell_types[ID($demux)] = "*";
if (edges) {
- cell_types[ID($mux)] = "*";
cell_types[ID($pmux)] = "*";
}