aboutsummaryrefslogtreecommitdiffstats
path: root/passes
diff options
context:
space:
mode:
Diffstat (limited to 'passes')
-rw-r--r--passes/cmds/Makefile.inc3
-rw-r--r--passes/cmds/bugpoint.cc2
-rw-r--r--passes/cmds/clean_zerowidth.cc210
-rw-r--r--passes/cmds/glift.cc599
-rw-r--r--passes/cmds/select.cc18
-rw-r--r--passes/cmds/show.cc60
-rw-r--r--passes/cmds/sta.cc312
-rw-r--r--passes/cmds/stat.cc6
-rw-r--r--passes/hierarchy/hierarchy.cc32
-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.cc10
-rw-r--r--passes/memory/memory_bmux2rom.cc87
-rw-r--r--passes/memory/memory_bram.cc28
-rw-r--r--passes/memory/memory_dff.cc24
-rw-r--r--passes/memory/memory_libmap.cc2093
-rw-r--r--passes/memory/memory_share.cc20
-rw-r--r--passes/opt/Makefile.inc1
-rw-r--r--passes/opt/opt.cc1
-rw-r--r--passes/opt/opt_dff.cc389
-rw-r--r--passes/opt/opt_expr.cc14
-rw-r--r--passes/opt/opt_ffinv.cc258
-rw-r--r--passes/opt/opt_lut_ins.cc10
-rw-r--r--passes/opt/opt_mem.cc156
-rw-r--r--passes/opt/opt_mem_priority.cc2
-rw-r--r--passes/opt/opt_merge.cc22
-rw-r--r--passes/opt/opt_reduce.cc397
-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_dff.cc39
-rw-r--r--passes/proc/proc_rom.cc252
-rw-r--r--passes/sat/Makefile.inc2
-rw-r--r--passes/sat/async2sync.cc70
-rw-r--r--passes/sat/clk2fflogic.cc94
-rw-r--r--passes/sat/sim.cc1607
-rw-r--r--passes/techmap/Makefile.inc2
-rw-r--r--passes/techmap/abc.cc412
-rw-r--r--passes/techmap/abc9.cc8
-rw-r--r--passes/techmap/abc9_ops.cc82
-rw-r--r--passes/techmap/bmuxmap.cc76
-rw-r--r--passes/techmap/demuxmap.cc80
-rw-r--r--passes/techmap/dfflegalize.cc1866
-rw-r--r--passes/techmap/dffunmap.cc13
-rw-r--r--passes/techmap/extract_reduce.cc107
-rw-r--r--passes/techmap/flatten.cc7
-rw-r--r--passes/techmap/iopadmap.cc31
-rw-r--r--passes/techmap/simplemap.cc332
-rw-r--r--passes/techmap/simplemap.h7
-rw-r--r--passes/techmap/techmap.cc4
-rw-r--r--passes/techmap/tribuf.cc49
-rw-r--r--passes/techmap/zinit.cc123
-rw-r--r--passes/tests/test_cell.cc46
55 files changed, 9725 insertions, 2135 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc
index 53bfd40c6..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
@@ -40,3 +41,5 @@ endif
OBJS += passes/cmds/scratchpad.o
OBJS += passes/cmds/logger.o
OBJS += passes/cmds/printattrs.o
+OBJS += passes/cmds/sta.o
+OBJS += passes/cmds/clean_zerowidth.o
diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc
index 16ac5b6a7..7b621504d 100644
--- a/passes/cmds/bugpoint.cc
+++ b/passes/cmds/bugpoint.cc
@@ -377,7 +377,7 @@ struct BugpointPass : public Pass {
if (wire->get_bool_attribute(ID::bugpoint_keep))
continue;
- if (wire->name.begins_with("$delete_wire"))
+ if (wire->name.begins_with("$delete_wire") || wire->name.begins_with("$auto$bugpoint"))
continue;
if (index++ == seed)
diff --git a/passes/cmds/clean_zerowidth.cc b/passes/cmds/clean_zerowidth.cc
new file mode 100644
index 000000000..bac6b1521
--- /dev/null
+++ b/passes/cmds/clean_zerowidth.cc
@@ -0,0 +1,210 @@
+/*
+ * 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 "kernel/yosys.h"
+#include "kernel/celltypes.h"
+#include "kernel/mem.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct CleanZeroWidthPass : public Pass {
+ CleanZeroWidthPass() : Pass("clean_zerowidth", "clean zero-width connections from the design") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" clean_zerowidth [selection]\n");
+ log("\n");
+ log("Fixes the selected cells and processes to contain no zero-width connections.\n");
+ log("Depending on the cell type, this may be implemented by removing the connection,\n");
+ log("widening it to 1-bit, or removing the cell altogether.\n");
+ log("\n");
+ }
+
+ void clean_case(RTLIL::CaseRule *cs)
+ {
+ std::vector<SigSig> new_actions;
+ for (auto &action : cs->actions)
+ if (GetSize(action.first) != 0)
+ new_actions.push_back(action);
+ std::swap(new_actions, cs->actions);
+ for (auto sw : cs->switches)
+ for (auto scs : sw->cases)
+ clean_case(scs);
+ }
+
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++)
+ {
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ CellTypes ct;
+ ct.setup();
+
+ for (auto module : design->selected_modules())
+ {
+ for (auto cell : module->selected_cells())
+ {
+ if (!ct.cell_known(cell->type)) {
+ // User-defined cell: just prune zero-width connections.
+ for (auto it: cell->connections()) {
+ if (GetSize(it.second) == 0) {
+ cell->unsetPort(it.first);
+ }
+ }
+ } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
+ // Coarse FF cells: remove if WIDTH == 0 (no outputs).
+ // This will also trigger on fine cells, so use the Q port
+ // width instead of actual WIDTH parameter.
+ if (GetSize(cell->getPort(ID::Q)) == 0) {
+ module->remove(cell);
+ }
+ } 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) {
+ module->remove(cell);
+ }
+ if (cell->getParam(ID::S_WIDTH).as_int() == 0) {
+ module->connect(cell->getPort(ID::Y), cell->getPort(ID::A));
+ module->remove(cell);
+ }
+ } else if (cell->type == ID($concat)) {
+ // If a concat has a zero-width input: replace with direct
+ // connection to the other input.
+ if (cell->getParam(ID::A_WIDTH).as_int() == 0) {
+ module->connect(cell->getPort(ID::Y), cell->getPort(ID::B));
+ module->remove(cell);
+ } else if (cell->getParam(ID::B_WIDTH).as_int() == 0) {
+ module->connect(cell->getPort(ID::Y), cell->getPort(ID::A));
+ module->remove(cell);
+ }
+ } else if (cell->type == ID($fsm)) {
+ // TODO: not supported
+ } else if (cell->is_mem_cell()) {
+ // Skip — will be handled below.
+ } else if (cell->type == ID($lut)) {
+ // Zero-width LUT is just a const driver.
+ if (cell->getParam(ID::WIDTH).as_int() == 0) {
+ module->connect(cell->getPort(ID::Y), cell->getParam(ID::LUT)[0]);
+ module->remove(cell);
+ }
+ } else if (cell->type == ID($sop)) {
+ // Zero-width SOP is just a const driver.
+ if (cell->getParam(ID::WIDTH).as_int() == 0) {
+ // The value is 1 iff DEPTH is non-0.
+ bool val = cell->getParam(ID::DEPTH).as_int() != 0;
+ module->connect(cell->getPort(ID::Y), val);
+ module->remove(cell);
+ }
+ } else if (cell->hasParam(ID::WIDTH)) {
+ // For cells with WIDTH parameter: remove if zero.
+ if (cell->getParam(ID::WIDTH).as_int() == 0) {
+ module->remove(cell);
+ }
+ } else if (cell->hasParam(ID::Y_WIDTH)) {
+ // For most operators: remove if Y width is 0, expand
+ // A and B to 1-bit if their width is 0.
+ if (cell->getParam(ID::Y_WIDTH).as_int() == 0) {
+ module->remove(cell);
+ } else if (cell->type == ID($macc)) {
+ // TODO: fixing zero-width A and B not supported.
+ } else {
+ if (cell->getParam(ID::A_WIDTH).as_int() == 0) {
+ cell->setPort(ID::A, State::S0);
+ cell->setParam(ID::A_WIDTH, 1);
+ }
+ if (cell->hasParam(ID::B_WIDTH) && cell->getParam(ID::B_WIDTH).as_int() == 0) {
+ cell->setPort(ID::B, State::S0);
+ cell->setParam(ID::B_WIDTH, 1);
+ }
+ }
+ }
+ }
+
+ // NOTE: Zero-width switch signals are left alone for processes, as there
+ // is no simple way of cleaning them up.
+ for (auto &it: module->processes) {
+ if (!design->selected(module, it.second))
+ continue;
+ clean_case(&it.second->root_case);
+ for (auto sync : it.second->syncs) {
+ std::vector<int> swizzle;
+ std::vector<RTLIL::MemWriteAction> new_memwr_actions;
+ for (int i = 0; i < GetSize(sync->mem_write_actions); i++) {
+ auto &memwr = sync->mem_write_actions[i];
+ if (GetSize(memwr.data) == 0)
+ continue;
+ if (GetSize(memwr.address) == 0)
+ memwr.address = State::S0;
+ Const priority_mask;
+ for (auto x : swizzle) {
+ priority_mask.bits.push_back(memwr.priority_mask.bits[x]);
+ }
+ memwr.priority_mask = priority_mask;
+ swizzle.push_back(i);
+ new_memwr_actions.push_back(memwr);
+ }
+ std::swap(new_memwr_actions, sync->mem_write_actions);
+ std::vector<SigSig> new_actions;
+ for (auto &action : sync->actions)
+ if (GetSize(action.first) != 0)
+ new_actions.push_back(action);
+ std::swap(new_actions, sync->actions);
+ }
+ }
+
+ for (auto &mem : Mem::get_selected_memories(module)) {
+ if (mem.width == 0) {
+ mem.remove();
+ continue;
+ }
+ for (auto &init : mem.inits) {
+ if (GetSize(init.addr) == 0) {
+ init.addr = State::S0;
+ }
+ }
+ for (auto &port : mem.rd_ports) {
+ if (GetSize(port.addr) == 0) {
+ port.addr = State::S0;
+ }
+ }
+ for (auto &port : mem.wr_ports) {
+ if (GetSize(port.addr) == 0) {
+ port.addr = State::S0;
+ }
+ }
+ mem.emit();
+ }
+
+ std::vector<SigSig> new_conns;
+ for (auto &conn : module->connections())
+ if (GetSize(conn.first) != 0)
+ new_conns.push_back(conn);
+ module->new_connections(new_conns);
+ }
+ }
+} CleanZeroWidthPass;
+
+PRIVATE_NAMESPACE_END
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..d609c8d0f 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");
@@ -1488,7 +1490,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 +1505,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 750fe0e10..35aa410e4 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;
@@ -239,6 +239,19 @@ struct ShowWorker
int idx = single_idx_count++;
for (int rep, i = int(sig.chunks().size())-1; i >= 0; i -= rep) {
const RTLIL::SigChunk &c = sig.chunks().at(i);
+ int cl, cr;
+ if (c.wire) {
+ if (c.wire->upto) {
+ cr = c.wire->start_offset + (c.wire->width - c.offset - 1);
+ cl = cr - (c.width - 1);
+ } else {
+ cr = c.wire->start_offset + c.offset;
+ cl = cr + c.width - 1;
+ }
+ } else {
+ cl = c.offset + c.width - 1;
+ cr = c.offset;
+ }
if (!driver && c.wire == nullptr) {
RTLIL::State s1 = c.data.front();
for (auto s2 : c.data)
@@ -254,9 +267,8 @@ struct ShowWorker
std::string repinfo = rep > 1 ? stringf("%dx ", rep) : "";
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(), c.offset+c.width-1, c.offset);
- net_conn_map[net].in.insert(stringf("x%d:s%d", idx, i));
- net_conn_map[net].bits = rep*c.width;
+ 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), rep*c.width});
net_conn_map[net].color = nextColor(c, net_conn_map[net].color);
} else
if (net.empty()) {
@@ -268,9 +280,8 @@ struct ShowWorker
c.data.front() == State::Sz ? 'Z' : '?',
pos, pos-rep*c.width+1);
} else {
- label_string += stringf("<s%d> %s%d:%d - %d:%d |", i, repinfo.c_str(), c.offset+c.width-1, c.offset, 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;
+ 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), rep*c.width});
net_conn_map[net].color = nextColor(c, net_conn_map[net].color);
}
pos -= rep * c.width;
@@ -280,6 +291,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
@@ -292,10 +304,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)
@@ -465,8 +476,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);
}
@@ -474,8 +484,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);
}
@@ -509,17 +518,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++);
}
}
@@ -529,12 +536,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)
@@ -543,9 +551,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");
@@ -653,7 +661,7 @@ struct ShowPass : public Pass {
log(" (including inout ports) are on the right side.\n");
log("\n");
log(" -pause\n");
- log(" wait for the use to press enter to before returning\n");
+ log(" wait for the user to press enter to before returning\n");
log("\n");
log(" -enum\n");
log(" enumerate objects with internal ($-prefixed) names\n");
diff --git a/passes/cmds/sta.cc b/passes/cmds/sta.cc
new file mode 100644
index 000000000..13e1ee13c
--- /dev/null
+++ b/passes/cmds/sta.cc
@@ -0,0 +1,312 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * (C) 2019 Eddie Hung <eddie@fpgeh.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/yosys.h"
+#include "kernel/sigtools.h"
+#include "kernel/timinginfo.h"
+#include <deque>
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct StaWorker
+{
+ Design *design;
+ Module *module;
+ SigMap sigmap;
+
+ struct t_data {
+ Cell* driver;
+ IdString dst_port, src_port;
+ vector<tuple<SigBit,int,IdString>> fanouts;
+ SigBit backtrack;
+ t_data() : driver(nullptr) {}
+ };
+ dict<SigBit, t_data> data;
+ std::deque<SigBit> queue;
+ struct t_endpoint {
+ Cell *sink;
+ IdString port;
+ int required;
+ t_endpoint() : sink(nullptr), required(0) {}
+ };
+ dict<SigBit, t_endpoint> endpoints;
+
+ int maxarrival;
+ SigBit maxbit;
+
+ pool<SigBit> driven;
+
+ StaWorker(RTLIL::Module *module) : design(module->design), module(module), sigmap(module), maxarrival(0)
+ {
+ TimingInfo timing;
+
+ 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));
+ continue;
+ }
+
+ if (!inst_module->get_blackbox_attribute()) {
+ log_warning("Cell type '%s' is not a black- nor white-box! Ignoring.\n", log_id(cell->type));
+ continue;
+ }
+
+ IdString derived_type = inst_module->derive(design, cell->parameters);
+ inst_module = design->module(derived_type);
+ log_assert(inst_module);
+
+ if (!timing.count(derived_type)) {
+ auto &t = timing.setup_module(inst_module);
+ if (t.has_inputs && t.comb.empty() && t.arrival.empty() && t.required.empty())
+ log_warning("Module '%s' has no timing arcs!\n", log_id(cell->type));
+ }
+
+ auto &t = timing.at(derived_type);
+ if (t.comb.empty() && t.arrival.empty() && t.required.empty())
+ continue;
+
+ pool<std::pair<SigBit,TimingInfo::NameBit>> src_bits, dst_bits;
+
+ for (auto &conn : cell->connections()) {
+ auto rhs = sigmap(conn.second);
+ for (auto i = 0; i < GetSize(rhs); i++) {
+ const auto &bit = rhs[i];
+ if (!bit.wire)
+ continue;
+ TimingInfo::NameBit namebit(conn.first,i);
+ if (cell->input(conn.first)) {
+ src_bits.insert(std::make_pair(bit,namebit));
+
+ auto it = t.required.find(namebit);
+ if (it == t.required.end())
+ continue;
+ auto r = endpoints.insert(bit);
+ if (r.second || r.first->second.required < it->second.first) {
+ r.first->second.sink = cell;
+ r.first->second.port = conn.first;
+ r.first->second.required = it->second.first;
+ }
+ }
+ if (cell->output(conn.first)) {
+ dst_bits.insert(std::make_pair(bit,namebit));
+ auto &d = data[bit];
+ d.driver = cell;
+ d.dst_port = conn.first;
+ driven.insert(bit);
+
+ auto it = t.arrival.find(namebit);
+ if (it == t.arrival.end())
+ continue;
+ const auto &s = it->second.second;
+ if (cell->hasPort(s.name)) {
+ auto s_bit = sigmap(cell->getPort(s.name)[s.offset]);
+ if (s_bit.wire)
+ data[s_bit].fanouts.emplace_back(bit,it->second.first,s.name);
+ }
+ }
+ }
+ }
+
+ for (const auto &s : src_bits)
+ for (const auto &d : dst_bits) {
+ auto it = t.comb.find(TimingInfo::BitBit(s.second,d.second));
+ if (it == t.comb.end())
+ continue;
+ data[s.first].fanouts.emplace_back(d.first,it->second,s.second.name);
+ }
+ }
+
+ for (auto port_name : module->ports) {
+ auto wire = module->wire(port_name);
+ if (wire->port_input) {
+ for (const auto &b : sigmap(wire)) {
+ queue.emplace_back(b);
+ driven.insert(b);
+ }
+ // All primary inputs to arrive at time zero
+ wire->set_intvec_attribute(ID::sta_arrival, std::vector<int>(GetSize(wire), 0));
+ }
+ if (wire->port_output)
+ for (const auto &b : sigmap(wire))
+ if (b.wire)
+ endpoints.insert(b);
+ }
+ }
+
+ void run()
+ {
+ while (!queue.empty()) {
+ auto b = queue.front();
+ queue.pop_front();
+ auto it = data.find(b);
+ if (it == data.end())
+ continue;
+ const auto& src_arrivals = b.wire->get_intvec_attribute(ID::sta_arrival);
+ log_assert(GetSize(src_arrivals) == GetSize(b.wire));
+ auto src_arrival = src_arrivals[b.offset];
+ for (const auto &d : it->second.fanouts) {
+ const auto &dst_bit = std::get<0>(d);
+ auto dst_arrivals = dst_bit.wire->get_intvec_attribute(ID::sta_arrival);
+ if (dst_arrivals.empty())
+ dst_arrivals = std::vector<int>(GetSize(dst_bit.wire), -1);
+ else
+ log_assert(GetSize(dst_arrivals) == GetSize(dst_bit.wire));
+ auto &dst_arrival = dst_arrivals[dst_bit.offset];
+ auto new_arrival = src_arrival + std::get<1>(d);
+ if (dst_arrival < new_arrival) {
+ auto dst_wire = dst_bit.wire;
+ dst_arrival = std::max(dst_arrival, new_arrival);
+ dst_wire->set_intvec_attribute(ID::sta_arrival, dst_arrivals);
+ queue.emplace_back(dst_bit);
+
+ data[dst_bit].backtrack = b;
+ data[dst_bit].src_port = std::get<2>(d);
+
+ auto it = endpoints.find(dst_bit);
+ if (it != endpoints.end())
+ new_arrival += it->second.required;
+ if (new_arrival > maxarrival && driven.count(b)) {
+ maxarrival = new_arrival;
+ maxbit = dst_bit;
+ }
+ }
+ }
+ }
+
+ auto b = maxbit;
+ if (b == SigBit()) {
+ log("No timing paths found.\n");
+ return;
+ }
+
+ log("Latest arrival time in '%s' is %d:\n", log_id(module), maxarrival);
+ auto it = endpoints.find(maxbit);
+ if (it != endpoints.end() && it->second.sink)
+ log(" %6d %s (%s.%s)\n", maxarrival, log_id(it->second.sink), log_id(it->second.sink->type), log_id(it->second.port));
+ else {
+ log(" %6d (%s)\n", maxarrival, b.wire->port_output ? "<primary output>" : "<unknown>");
+ if (!b.wire->port_output)
+ log_warning("Critical-path does not terminate in a recognised endpoint.\n");
+ }
+ auto jt = data.find(b);
+ while (jt != data.end()) {
+ int arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset];
+ if (jt->second.driver) {
+ log(" %s\n", log_signal(b));
+ log(" %6d %s (%s.%s->%s)\n", arrival, log_id(jt->second.driver), log_id(jt->second.driver->type), log_id(jt->second.src_port), log_id(jt->second.dst_port));
+ }
+ else if (b.wire->port_input)
+ log(" %6d %s (%s)\n", arrival, log_signal(b), "<primary input>");
+ else
+ log_abort();
+ b = jt->second.backtrack;
+ jt = data.find(b);
+ }
+
+ std::map<int, unsigned> arrival_histogram;
+ for (const auto &i : endpoints) {
+ const auto &b = i.first;
+ if (!driven.count(b))
+ continue;
+
+ if (!b.wire->attributes.count(ID::sta_arrival)) {
+ log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b));
+ continue;
+ }
+
+ auto arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset];
+ if (arrival < 0) {
+ log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b));
+ continue;
+ }
+ arrival += i.second.required;
+ arrival_histogram[arrival]++;
+ }
+ // Adapted from https://github.com/YosysHQ/nextpnr/blob/affb12cc27ebf409eade062c4c59bb98569d8147/common/timing.cc#L946-L969
+ if (arrival_histogram.size() > 0) {
+ unsigned num_bins = 20;
+ unsigned bar_width = 60;
+ auto min_arrival = arrival_histogram.begin()->first;
+ auto max_arrival = arrival_histogram.rbegin()->first;
+ auto bin_size = std::max<unsigned>(1, ceil((max_arrival - min_arrival + 1) / float(num_bins)));
+ std::vector<unsigned> bins(num_bins);
+ unsigned max_freq = 0;
+ for (const auto &i : arrival_histogram) {
+ auto &bin = bins[(i.first - min_arrival) / bin_size];
+ bin += i.second;
+ max_freq = std::max(max_freq, bin);
+ }
+ bar_width = std::min(bar_width, max_freq);
+
+ log("\n");
+ log("Arrival histogram:\n");
+ log(" legend: * represents %d endpoint(s)\n", max_freq / bar_width);
+ log(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width);
+ for (int i = num_bins-1; i >= 0; --i)
+ log("(%6d, %6d] |%s%c\n", min_arrival + bin_size * (i + 1), min_arrival + bin_size * i,
+ std::string(bins[i] * bar_width / max_freq, '*').c_str(),
+ (bins[i] * bar_width) % max_freq > 0 ? '+' : ' ');
+ }
+ }
+};
+
+struct StaPass : public Pass {
+ StaPass() : Pass("sta", "perform static timing analysis") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" sta [options] [selection]\n");
+ log("\n");
+ log("This command performs static timing analysis on the design. (Only considers\n");
+ log("paths within a single module, so the design must be flattened.)\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ log_header(design, "Executing STA pass (static timing analysis).\n");
+
+ /*
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ if (args[argidx] == "-TODO") {
+ continue;
+ }
+ break;
+ }
+ */
+
+ extra_args(args, 1, design);
+
+ for (Module *module : design->selected_modules())
+ {
+ if (module->has_processes_warn())
+ continue;
+
+ StaWorker worker(module);
+ worker.run();
+ }
+ }
+} StaPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc
index 422810526..fffdda48e 100644
--- a/passes/cmds/stat.cc
+++ b/passes/cmds/stat.cc
@@ -117,10 +117,14 @@ 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),
- ID($dlatch), ID($adlatch), ID($dlatchsr)))
+ ID($aldff), ID($aldffe), ID($dlatch), ID($adlatch), ID($dlatchsr)))
cell_type = stringf("%s_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Q)));
}
diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc
index 650036580..d40d6e59f 100644
--- a/passes/hierarchy/hierarchy.cc
+++ b/passes/hierarchy/hierarchy.cc
@@ -554,10 +554,14 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check
// If any interface instances or interface ports were found in the module, we need to rederive it completely:
if ((if_expander.interfaces_in_module.size() > 0 || has_interface_ports) && !module->get_bool_attribute(ID::interfaces_replaced_in_module)) {
- module->reprocess_module(design, if_expander.interfaces_in_module);
+ module->expand_interfaces(design, if_expander.interfaces_in_module);
return did_something;
}
+ // Now that modules have been derived, we may want to reprocess this
+ // module given the additional available context.
+ if (module->reprocess_if_necessary(design))
+ return true;
for (auto &it : array_cells)
{
@@ -972,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"));
@@ -996,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..e34cc927e 100644
--- a/passes/memory/memory.cc
+++ b/passes/memory/memory.cc
@@ -31,13 +31,14 @@ 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] [-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_bmux2rom (skipped if called with -norom)\n");
log(" memory_dff (skipped if called with -nordff or -memx)\n");
log(" opt_clean\n");
log(" memory_share [-nowiden] [-nosat]\n");
@@ -54,6 +55,7 @@ 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;
@@ -65,6 +67,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;
@@ -97,6 +103,8 @@ 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, "opt_clean");
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 21962c238..91209d428 100644
--- a/passes/memory/memory_dff.cc
+++ b/passes/memory/memory_dff.cc
@@ -71,9 +71,9 @@ struct MemQueryCache
// port_ren is an upper bound on when we care about the value fetched
// from memory this cycle.
int ren = ezSAT::CONST_TRUE;
- if (ff.has_en) {
- ren = qcsat.importSigBit(ff.sig_en);
- if (!ff.pol_en)
+ if (ff.has_ce) {
+ ren = qcsat.importSigBit(ff.sig_ce);
+ if (!ff.pol_ce)
ren = qcsat.ez->NOT(ren);
}
if (ff.has_srst) {
@@ -347,6 +347,10 @@ struct MemoryDffWorker
log("output latches are not supported.\n");
return;
}
+ if (ff.has_aload) {
+ log("output FF has async load, not supported.\n");
+ return;
+ }
if (ff.has_sr) {
// Latches and FFs with SR are not supported.
log("output FF has both set and reset, not supported.\n");
@@ -491,8 +495,8 @@ struct MemoryDffWorker
log("merging output FF to cell.\n");
merger.remove_output_ff(bits);
- if (ff.has_en && !ff.pol_en)
- ff.sig_en = module->LogicNot(NEW_ID, ff.sig_en);
+ if (ff.has_ce && !ff.pol_ce)
+ ff.sig_ce = module->LogicNot(NEW_ID, ff.sig_ce);
if (ff.has_arst && !ff.pol_arst)
ff.sig_arst = module->LogicNot(NEW_ID, ff.sig_arst);
if (ff.has_srst && !ff.pol_srst)
@@ -500,8 +504,8 @@ struct MemoryDffWorker
port.clk = ff.sig_clk;
port.clk_enable = true;
port.clk_polarity = ff.pol_clk;
- if (ff.has_en)
- port.en = ff.sig_en;
+ if (ff.has_ce)
+ port.en = ff.sig_ce;
else
port.en = State::S1;
if (ff.has_arst) {
@@ -551,6 +555,10 @@ struct MemoryDffWorker
log("address latches are not supported.\n");
return;
}
+ if (ff.has_aload) {
+ log("address FF has async load, not supported.\n");
+ return;
+ }
if (ff.has_sr || ff.has_arst) {
log("address FF has async set and/or reset, not supported.\n");
return;
@@ -573,7 +581,7 @@ struct MemoryDffWorker
// Now we're commited to merge it.
merger.mark_input_ff(bits);
// If the address FF has enable and/or sync reset, unmap it.
- ff.unmap_ce_srst(module);
+ ff.unmap_ce_srst();
port.clk = ff.sig_clk;
port.en = State::S1;
port.addr = ff.sig_d;
diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc
new file mode 100644
index 000000000..ab7bb7bb2
--- /dev/null
+++ b/passes/memory/memory_libmap.cc
@@ -0,0 +1,2093 @@
+/*
+ * 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);
+ 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 9d82739aa..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)
@@ -416,7 +430,9 @@ struct MemoryShareWorker
else
this_addr.extend_u0(GetSize(last_addr));
- port1.addr = module->Mux(NEW_ID, last_addr, this_addr, this_en_active);
+ SigSpec new_addr = module->Mux(NEW_ID, last_addr.extract_end(port1.wide_log2), this_addr.extract_end(port1.wide_log2), this_en_active);
+
+ port1.addr = SigSpec({new_addr, port1.addr.extract(0, port1.wide_log2)});
port1.data = module->Mux(NEW_ID, last_data, this_data, this_en_active);
std::map<std::pair<RTLIL::SigBit, RTLIL::SigBit>, int> groups_en;
@@ -539,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 ddf08392b..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)
@@ -275,7 +272,7 @@ struct OptDffWorker
bool changed = false;
if (!ff.width) {
- module->remove(cell);
+ ff.remove();
did_something = true;
continue;
}
@@ -316,6 +313,7 @@ struct OptDffWorker
continue;
}
ff = ff.slice(keep_bits);
+ ff.cell = cell;
changed = true;
}
@@ -382,6 +380,68 @@ struct OptDffWorker
}
}
+ if (ff.has_aload) {
+ if (ff.sig_aload == (ff.pol_aload ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_aload == State::Sx)) {
+ // Always-inactive enable — remove.
+ log("Removing never-active async load on %s (%s) from module %s.\n",
+ log_id(cell), log_id(cell->type), log_id(module));
+ ff.has_aload = false;
+ changed = true;
+ } else if (ff.sig_aload == (ff.pol_aload ? State::S1 : State::S0)) {
+ // Always-active enable. Make a comb circuit, nuke the FF/latch.
+ log("Handling always-active async load on %s (%s) from module %s (changing to combinatorial circuit).\n",
+ log_id(cell), log_id(cell->type), log_id(module));
+ ff.remove();
+ if (ff.has_sr) {
+ SigSpec tmp;
+ if (ff.is_fine) {
+ if (ff.pol_set)
+ tmp = module->MuxGate(NEW_ID, ff.sig_ad, State::S1, ff.sig_set);
+ else
+ tmp = module->MuxGate(NEW_ID, State::S1, ff.sig_ad, ff.sig_set);
+ if (ff.pol_clr)
+ module->addMuxGate(NEW_ID, tmp, State::S0, ff.sig_clr, ff.sig_q);
+ else
+ module->addMuxGate(NEW_ID, State::S0, tmp, ff.sig_clr, ff.sig_q);
+ } else {
+ if (ff.pol_set)
+ tmp = module->Or(NEW_ID, ff.sig_ad, ff.sig_set);
+ else
+ tmp = module->Or(NEW_ID, ff.sig_ad, module->Not(NEW_ID, ff.sig_set));
+ if (ff.pol_clr)
+ module->addAnd(NEW_ID, tmp, module->Not(NEW_ID, ff.sig_clr), ff.sig_q);
+ else
+ module->addAnd(NEW_ID, tmp, ff.sig_clr, ff.sig_q);
+ }
+ } else if (ff.has_arst) {
+ if (ff.is_fine) {
+ if (ff.pol_arst)
+ module->addMuxGate(NEW_ID, ff.sig_ad, ff.val_arst[0], ff.sig_arst, ff.sig_q);
+ else
+ module->addMuxGate(NEW_ID, ff.val_arst[0], ff.sig_ad, ff.sig_arst, ff.sig_q);
+ } else {
+ if (ff.pol_arst)
+ module->addMux(NEW_ID, ff.sig_ad, ff.val_arst, ff.sig_arst, ff.sig_q);
+ else
+ module->addMux(NEW_ID, ff.val_arst, ff.sig_ad, ff.sig_arst, ff.sig_q);
+ }
+ } else {
+ module->connect(ff.sig_q, ff.sig_ad);
+ }
+ did_something = true;
+ continue;
+ } else if (ff.sig_ad.is_fully_const() && !ff.has_arst && !ff.has_sr) {
+ log("Changing const-value async load to async reset on %s (%s) from module %s.\n",
+ log_id(cell), log_id(cell->type), log_id(module));
+ ff.has_arst = true;
+ ff.has_aload = false;
+ ff.sig_arst = ff.sig_aload;
+ ff.pol_arst = ff.pol_aload;
+ ff.val_arst = ff.sig_ad.as_const();
+ changed = true;
+ }
+ }
+
if (ff.has_arst) {
if (ff.sig_arst == (ff.pol_arst ? State::S0 : State::S1)) {
// Always-inactive reset — remove.
@@ -393,8 +453,7 @@ struct OptDffWorker
// Always-active async reset — change to const driver.
log("Handling always-active ARST on %s (%s) from module %s (changing to const driver).\n",
log_id(cell), log_id(cell->type), log_id(module));
- initvals.remove_init(ff.sig_q);
- module->remove(cell);
+ ff.remove();
module->connect(ff.sig_q, ff.val_arst);
did_something = true;
continue;
@@ -414,111 +473,63 @@ struct OptDffWorker
log_id(cell), log_id(cell->type), log_id(module));
ff.has_srst = false;
if (!ff.ce_over_srst)
- ff.has_en = false;
- ff.sig_d = ff.val_d = ff.val_srst;
- ff.d_is_const = true;
+ ff.has_ce = false;
+ ff.sig_d = ff.val_srst;
changed = true;
}
}
- if (ff.has_en) {
- if (ff.sig_en == (ff.pol_en ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_en == State::Sx)) {
+ if (ff.has_ce) {
+ if (ff.sig_ce == (ff.pol_ce ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_ce == State::Sx)) {
// Always-inactive enable — remove.
- if (ff.has_clk && ff.has_srst && !ff.ce_over_srst) {
+ if (ff.has_srst && !ff.ce_over_srst) {
log("Handling never-active EN on %s (%s) from module %s (connecting SRST instead).\n",
log_id(cell), log_id(cell->type), log_id(module));
// FF with sync reset — connect the sync reset to D instead.
- ff.pol_en = ff.pol_srst;
- ff.sig_en = ff.sig_srst;
+ ff.pol_ce = ff.pol_srst;
+ ff.sig_ce = ff.sig_srst;
ff.has_srst = false;
- ff.sig_d = ff.val_d = ff.val_srst;
- ff.d_is_const = true;
+ ff.sig_d = ff.val_srst;
changed = true;
} else {
log("Handling never-active EN on %s (%s) from module %s (removing D path).\n",
log_id(cell), log_id(cell->type), log_id(module));
- // The D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver).
- ff.has_d = ff.has_en = ff.has_clk = false;
+ // The D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver).
+ ff.has_ce = ff.has_clk = ff.has_srst = false;
changed = true;
}
- } else if (ff.sig_en == (ff.pol_en ? State::S1 : State::S0)) {
- // Always-active enable.
- if (ff.has_clk) {
- // For FF, just remove the useless enable.
- log("Removing always-active EN on %s (%s) from module %s.\n",
- log_id(cell), log_id(cell->type), log_id(module));
- ff.has_en = false;
- changed = true;
- } else {
- // For latches, make a comb circuit, nuke the latch.
- log("Handling always-active EN on %s (%s) from module %s (changing to combinatorial circuit).\n",
- log_id(cell), log_id(cell->type), log_id(module));
- initvals.remove_init(ff.sig_q);
- module->remove(cell);
- if (ff.has_sr) {
- SigSpec tmp;
- if (ff.is_fine) {
- if (ff.pol_set)
- tmp = module->MuxGate(NEW_ID, ff.sig_d, State::S1, ff.sig_set);
- else
- tmp = module->MuxGate(NEW_ID, State::S1, ff.sig_d, ff.sig_set);
- if (ff.pol_clr)
- module->addMuxGate(NEW_ID, tmp, State::S0, ff.sig_clr, ff.sig_q);
- else
- module->addMuxGate(NEW_ID, State::S0, tmp, ff.sig_clr, ff.sig_q);
- } else {
- if (ff.pol_set)
- tmp = module->Or(NEW_ID, ff.sig_d, ff.sig_set);
- else
- tmp = module->Or(NEW_ID, ff.sig_d, module->Not(NEW_ID, ff.sig_set));
- if (ff.pol_clr)
- module->addAnd(NEW_ID, tmp, module->Not(NEW_ID, ff.sig_clr), ff.sig_q);
- else
- module->addAnd(NEW_ID, tmp, ff.sig_clr, ff.sig_q);
- }
- } else if (ff.has_arst) {
- if (ff.is_fine) {
- if (ff.pol_arst)
- module->addMuxGate(NEW_ID, ff.sig_d, ff.val_arst[0], ff.sig_arst, ff.sig_q);
- else
- module->addMuxGate(NEW_ID, ff.val_arst[0], ff.sig_d, ff.sig_arst, ff.sig_q);
- } else {
- if (ff.pol_arst)
- module->addMux(NEW_ID, ff.sig_d, ff.val_arst, ff.sig_arst, ff.sig_q);
- else
- module->addMux(NEW_ID, ff.val_arst, ff.sig_d, ff.sig_arst, ff.sig_q);
- }
- } else {
- module->connect(ff.sig_q, ff.sig_d);
- }
- did_something = true;
- continue;
- }
+ } else if (ff.sig_ce == (ff.pol_ce ? State::S1 : State::S0)) {
+ // Always-active enable. Just remove it.
+ // For FF, just remove the useless enable.
+ log("Removing always-active EN on %s (%s) from module %s.\n",
+ log_id(cell), log_id(cell->type), log_id(module));
+ ff.has_ce = false;
+ changed = true;
}
}
if (ff.has_clk) {
if (ff.sig_clk.is_fully_const()) {
- // Const clock — the D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver).
+ // Const clock — the D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver).
log("Handling const CLK on %s (%s) from module %s (removing D path).\n",
log_id(cell), log_id(cell->type), log_id(module));
- ff.has_d = ff.has_en = ff.has_clk = ff.has_srst = false;
+ ff.has_ce = ff.has_clk = ff.has_srst = false;
changed = true;
}
}
- if (ff.has_d && ff.sig_d == ff.sig_q) {
+ if ((ff.has_clk || ff.has_gclk) && ff.sig_d == ff.sig_q) {
// Q wrapped back to D, can be removed.
if (ff.has_clk && ff.has_srst) {
// FF with sync reset — connect the sync reset to D instead.
log("Handling D = Q on %s (%s) from module %s (conecting SRST instead).\n",
log_id(cell), log_id(cell->type), log_id(module));
- if (ff.has_en && ff.ce_over_srst) {
- if (!ff.pol_en) {
+ if (ff.has_ce && ff.ce_over_srst) {
+ if (!ff.pol_ce) {
if (ff.is_fine)
- ff.sig_en = module->NotGate(NEW_ID, ff.sig_en);
+ ff.sig_ce = module->NotGate(NEW_ID, ff.sig_ce);
else
- ff.sig_en = module->Not(NEW_ID, ff.sig_en);
+ ff.sig_ce = module->Not(NEW_ID, ff.sig_ce);
}
if (!ff.pol_srst) {
if (ff.is_fine)
@@ -527,96 +538,37 @@ struct OptDffWorker
ff.sig_srst = module->Not(NEW_ID, ff.sig_srst);
}
if (ff.is_fine)
- ff.sig_en = module->AndGate(NEW_ID, ff.sig_en, ff.sig_srst);
+ ff.sig_ce = module->AndGate(NEW_ID, ff.sig_ce, ff.sig_srst);
else
- ff.sig_en = module->And(NEW_ID, ff.sig_en, ff.sig_srst);
- ff.pol_en = true;
+ ff.sig_ce = module->And(NEW_ID, ff.sig_ce, ff.sig_srst);
+ ff.pol_ce = true;
} else {
- ff.pol_en = ff.pol_srst;
- ff.sig_en = ff.sig_srst;
+ ff.pol_ce = ff.pol_srst;
+ ff.sig_ce = ff.sig_srst;
}
- ff.has_en = true;
+ ff.has_ce = true;
ff.has_srst = false;
- ff.sig_d = ff.val_d = ff.val_srst;
- ff.d_is_const = true;
+ ff.sig_d = ff.val_srst;
changed = true;
} else {
// 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_d = ff.has_en = ff.has_clk = false;
+ ff.has_gclk = ff.has_clk = ff.has_ce = false;
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_d) {
- 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;
- }
- }
- 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);
+ if (ff.has_aload && !ff.has_clk && ff.sig_ad == ff.sig_q) {
+ log("Handling AD = Q on %s (%s) from module %s (removing async load path).\n",
+ log_id(cell), log_id(cell->type), log_id(module));
+ ff.has_aload = false;
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_en || ff.ce_over_srst) && !opt.nosdff) {
+ if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_ce || ff.ce_over_srst) && !opt.nosdff) {
// Try to merge sync resets.
std::map<ctrls_t, std::vector<int>> groups;
std::vector<int> remaining_indices;
@@ -677,9 +629,9 @@ struct OptDffWorker
new_ff.has_srst = true;
new_ff.sig_srst = srst.first;
new_ff.pol_srst = srst.second;
- if (new_ff.has_en)
+ if (new_ff.has_ce)
new_ff.ce_over_srst = true;
- Cell *new_cell = new_ff.emit(module, NEW_ID);
+ Cell *new_cell = new_ff.emit();
if (new_cell)
dff_cells.push_back(new_cell);
log("Adding SRST signal on %s (%s) from module %s (D = %s, Q = %s, rval = %s).\n",
@@ -692,10 +644,11 @@ struct OptDffWorker
continue;
} else if (GetSize(remaining_indices) != ff.width) {
ff = ff.slice(remaining_indices);
+ ff.cell = cell;
changed = true;
}
}
- if ((!ff.has_srst || !ff.has_en || !ff.ce_over_srst) && !opt.nodffe) {
+ if ((!ff.has_srst || !ff.has_ce || !ff.ce_over_srst) && !opt.nodffe) {
// Try to merge enables.
std::map<std::pair<patterns_t, ctrls_t>, std::vector<int>> groups;
std::vector<int> remaining_indices;
@@ -725,8 +678,8 @@ struct OptDffWorker
if (!opt.simple_dffe)
patterns = find_muxtree_feedback_patterns(ff.sig_d[i], ff.sig_q[i], pattern_t());
if (!patterns.empty() || !enables.empty()) {
- if (ff.has_en)
- enables.insert(ctrl_t(ff.sig_en, ff.pol_en));
+ if (ff.has_ce)
+ enables.insert(ctrl_t(ff.sig_ce, ff.pol_ce));
simplify_patterns(patterns);
groups[std::make_pair(patterns, enables)].push_back(i);
} else
@@ -737,11 +690,11 @@ struct OptDffWorker
FfData new_ff = ff.slice(it.second);
ctrl_t en = make_patterns_logic(it.first.first, it.first.second, ff.is_fine);
- new_ff.has_en = true;
- new_ff.sig_en = en.first;
- new_ff.pol_en = en.second;
+ new_ff.has_ce = true;
+ new_ff.sig_ce = en.first;
+ new_ff.pol_ce = en.second;
new_ff.ce_over_srst = false;
- Cell *new_cell = new_ff.emit(module, NEW_ID);
+ Cell *new_cell = new_ff.emit();
if (new_cell)
dff_cells.push_back(new_cell);
log("Adding EN signal on %s (%s) from module %s (D = %s, Q = %s).\n",
@@ -754,6 +707,7 @@ struct OptDffWorker
continue;
} else if (GetSize(remaining_indices) != ff.width) {
ff = ff.slice(remaining_indices);
+ ff.cell = cell;
changed = true;
}
}
@@ -761,9 +715,116 @@ struct OptDffWorker
if (changed) {
// Rebuild the FF.
- IdString name = cell->name;
- module->remove(cell);
- ff.emit(module, name);
+ ff.emit();
+ did_something = true;
+ }
+ }
+ 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;
}
}
@@ -845,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_expr.cc b/passes/opt/opt_expr.cc
index cdd821c52..be0cd470b 100644
--- a/passes/opt/opt_expr.cc
+++ b/passes/opt/opt_expr.cc
@@ -441,7 +441,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (!noclkinv)
{
- if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2)))
+ if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2)))
handle_polarity_inv(cell, ID::CLK, ID::CLK_POLARITY, assign_map, invert_map);
if (cell->type.in(ID($sr), ID($dffsr), ID($dffsre), ID($dlatchsr))) {
@@ -452,10 +452,13 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (cell->type.in(ID($adff), ID($adffe), ID($adlatch)))
handle_polarity_inv(cell, ID::ARST, ID::ARST_POLARITY, assign_map, invert_map);
+ if (cell->type.in(ID($aldff), ID($aldffe)))
+ handle_polarity_inv(cell, ID::ALOAD, ID::ALOAD_POLARITY, assign_map, invert_map);
+
if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce)))
handle_polarity_inv(cell, ID::SRST, ID::SRST_POLARITY, assign_map, invert_map);
- if (cell->type.in(ID($dffe), ID($adffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr)))
+ if (cell->type.in(ID($dffe), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr)))
handle_polarity_inv(cell, ID::EN, ID::EN_POLARITY, assign_map, invert_map);
handle_clkpol_celltype_swap(cell, "$_SR_N?_", "$_SR_P?_", ID::S, assign_map, invert_map);
@@ -484,6 +487,13 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
handle_clkpol_celltype_swap(cell, "$_SDFFCE_?N??_", "$_SDFFCE_?P??_", ID::R, assign_map, invert_map);
handle_clkpol_celltype_swap(cell, "$_SDFFCE_???N_", "$_SDFFCE_???P_", ID::E, assign_map, invert_map);
+ handle_clkpol_celltype_swap(cell, "$_ALDFF_N?_", "$_ALDFF_P?_", ID::C, assign_map, invert_map);
+ handle_clkpol_celltype_swap(cell, "$_ALDFF_?N_", "$_ALDFF_?P_", ID::L, assign_map, invert_map);
+
+ handle_clkpol_celltype_swap(cell, "$_ALDFFE_N??_", "$_ALDFFE_P??_", ID::C, assign_map, invert_map);
+ handle_clkpol_celltype_swap(cell, "$_ALDFFE_?N?_", "$_ALDFFE_?P?_", ID::L, assign_map, invert_map);
+ handle_clkpol_celltype_swap(cell, "$_ALDFFE_??N_", "$_ALDFFE_??P_", ID::E, assign_map, invert_map);
+
handle_clkpol_celltype_swap(cell, "$_DFFSR_N??_", "$_DFFSR_P??_", ID::C, assign_map, invert_map);
handle_clkpol_celltype_swap(cell, "$_DFFSR_?N?_", "$_DFFSR_?P?_", ID::S, assign_map, invert_map);
handle_clkpol_celltype_swap(cell, "$_DFFSR_??N_", "$_DFFSR_??P_", ID::R, assign_map, invert_map);
diff --git a/passes/opt/opt_ffinv.cc b/passes/opt/opt_ffinv.cc
new file mode 100644
index 000000000..fd76dd2be
--- /dev/null
+++ b/passes/opt/opt_ffinv.cc
@@ -0,0 +1,258 @@
+/*
+ * 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.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.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");
+
+ for (Cell *cell : module->selected_cells()) {
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type))
+ continue;
+
+ 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_lut_ins.cc b/passes/opt/opt_lut_ins.cc
index 99043ef7e..2f7c392b2 100644
--- a/passes/opt/opt_lut_ins.cc
+++ b/passes/opt/opt_lut_ins.cc
@@ -193,6 +193,12 @@ struct OptLutInsPass : public Pass {
swz += extra;
}
}
+ if (techname == "gowin") {
+ // Pad the LUT to 1 input, adding consts from the front.
+ if (new_inputs.empty()) {
+ new_inputs.insert(new_inputs.begin(), State::S0);
+ }
+ }
Const new_lut(0, 1 << GetSize(new_inputs));
for (int i = 0; i < GetSize(new_lut); i++) {
int lidx = 0;
@@ -209,9 +215,9 @@ struct OptLutInsPass : public Pass {
}
new_lut[i] = lut[lidx];
}
- // For ecp5, do not replace with a const driver — the nextpnr
+ // For ecp5, and gowin do not replace with a const driver — the nextpnr
// packer requires a complete set of LUTs for wide LUT muxes.
- if (new_inputs.empty() && techname != "ecp5") {
+ if (new_inputs.empty() && techname != "ecp5" && techname != "gowin") {
// const driver.
remove_cells.push_back(cell);
module->connect(output, new_lut[0]);
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_mem_priority.cc b/passes/opt/opt_mem_priority.cc
index 49ece570b..a9b145bea 100644
--- a/passes/opt/opt_mem_priority.cc
+++ b/passes/opt/opt_mem_priority.cc
@@ -34,7 +34,7 @@ struct OptMemPriorityPass : public Pass {
log(" opt_mem_priority [selection]\n");
log("\n");
log("This pass detects cases where one memory write port has priority over another\n");
- log("even though they can never collide with each other — ie. there can never be\n");
+ log("even though they can never collide with each other -- ie. there can never be\n");
log("a situation where a given memory bit is written by both ports at the same\n");
log("time, for example because of always-different addresses, or mutually exclusive\n");
log("enable signals. In such cases, the priority relation is removed.\n");
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/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_dff.cc b/passes/proc/proc_dff.cc
index da2a14c82..234671df5 100644
--- a/passes/proc/proc_dff.cc
+++ b/passes/proc/proc_dff.cc
@@ -143,48 +143,23 @@ void gen_dffsr_complex(RTLIL::Module *mod, RTLIL::SigSpec sig_d, RTLIL::SigSpec
cell->type.c_str(), cell->name.c_str(), clk_polarity ? "positive" : "negative");
}
-void gen_dffsr(RTLIL::Module *mod, RTLIL::SigSpec sig_in, RTLIL::SigSpec sig_set, RTLIL::SigSpec sig_out,
+void gen_aldff(RTLIL::Module *mod, RTLIL::SigSpec sig_in, RTLIL::SigSpec sig_set, RTLIL::SigSpec sig_out,
bool clk_polarity, bool set_polarity, RTLIL::SigSpec clk, RTLIL::SigSpec set, RTLIL::Process *proc)
{
std::stringstream sstr;
sstr << "$procdff$" << (autoidx++);
- RTLIL::SigSpec sig_set_inv = mod->addWire(NEW_ID, sig_in.size());
- RTLIL::SigSpec sig_sr_set = mod->addWire(NEW_ID, sig_in.size());
- RTLIL::SigSpec sig_sr_clr = mod->addWire(NEW_ID, sig_in.size());
-
- RTLIL::Cell *inv_set = mod->addCell(NEW_ID, ID($not));
- inv_set->parameters[ID::A_SIGNED] = RTLIL::Const(0);
- inv_set->parameters[ID::A_WIDTH] = RTLIL::Const(sig_in.size());
- inv_set->parameters[ID::Y_WIDTH] = RTLIL::Const(sig_in.size());
- inv_set->setPort(ID::A, sig_set);
- inv_set->setPort(ID::Y, sig_set_inv);
-
- RTLIL::Cell *mux_sr_set = mod->addCell(NEW_ID, ID($mux));
- mux_sr_set->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size());
- mux_sr_set->setPort(set_polarity ? ID::A : ID::B, RTLIL::Const(0, sig_in.size()));
- mux_sr_set->setPort(set_polarity ? ID::B : ID::A, sig_set);
- mux_sr_set->setPort(ID::Y, sig_sr_set);
- mux_sr_set->setPort(ID::S, set);
-
- RTLIL::Cell *mux_sr_clr = mod->addCell(NEW_ID, ID($mux));
- mux_sr_clr->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size());
- mux_sr_clr->setPort(set_polarity ? ID::A : ID::B, RTLIL::Const(0, sig_in.size()));
- mux_sr_clr->setPort(set_polarity ? ID::B : ID::A, sig_set_inv);
- mux_sr_clr->setPort(ID::Y, sig_sr_clr);
- mux_sr_clr->setPort(ID::S, set);
-
- RTLIL::Cell *cell = mod->addCell(sstr.str(), ID($dffsr));
+ RTLIL::Cell *cell = mod->addCell(sstr.str(), ID($aldff));
cell->attributes = proc->attributes;
+
cell->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size());
+ cell->parameters[ID::ALOAD_POLARITY] = RTLIL::Const(set_polarity, 1);
cell->parameters[ID::CLK_POLARITY] = RTLIL::Const(clk_polarity, 1);
- cell->parameters[ID::SET_POLARITY] = RTLIL::Const(true, 1);
- cell->parameters[ID::CLR_POLARITY] = RTLIL::Const(true, 1);
cell->setPort(ID::D, sig_in);
cell->setPort(ID::Q, sig_out);
+ cell->setPort(ID::AD, sig_set);
cell->setPort(ID::CLK, clk);
- cell->setPort(ID::SET, sig_sr_set);
- cell->setPort(ID::CLR, sig_sr_clr);
+ cell->setPort(ID::ALOAD, set);
log(" created %s cell `%s' with %s edge clock and %s level non-const reset.\n", cell->type.c_str(), cell->name.c_str(),
clk_polarity ? "positive" : "negative", set_polarity ? "positive" : "negative");
@@ -355,7 +330,7 @@ void proc_dff(RTLIL::Module *mod, RTLIL::Process *proc, ConstEval &ce)
else if (!rstval.is_fully_const() && !ce.eval(rstval))
{
log_warning("Async reset value `%s' is not constant!\n", log_signal(rstval));
- gen_dffsr(mod, insig, rstval, sig_q,
+ gen_aldff(mod, insig, rstval, sig_q,
sync_edge->type == RTLIL::SyncType::STp,
sync_level && sync_level->type == RTLIL::SyncType::ST1,
sync_edge->signal, sync_level->signal, proc);
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/async2sync.cc b/passes/sat/async2sync.cc
index a2b51677e..46c76eba9 100644
--- a/passes/sat/async2sync.cc
+++ b/passes/sat/async2sync.cc
@@ -41,8 +41,6 @@ struct Async2syncPass : public Pass {
log("reset value in the next cycle regardless of the data-in value at the time of\n");
log("the clock edge.\n");
log("\n");
- log("Currently only $adff, $dffsr, and $dlatch cells are supported by this pass.\n");
- log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
@@ -74,16 +72,13 @@ struct Async2syncPass : public Pass {
FfData ff(&initvals, cell);
// Skip for $_FF_ and $ff cells.
- if (ff.has_d && !ff.has_clk && !ff.has_en)
+ if (ff.has_gclk)
continue;
if (ff.has_clk)
{
- if (!ff.has_sr && !ff.has_arst)
- continue;
-
if (ff.has_sr) {
- ff.unmap_ce_srst(module);
+ ff.unmap_ce_srst();
log("Replacing %s.%s (%s): SET=%s, CLR=%s, D=%s, Q=%s\n",
log_id(module), log_id(cell), log_id(cell->type),
@@ -128,8 +123,41 @@ struct Async2syncPass : public Pass {
ff.sig_d = new_d;
ff.sig_q = new_q;
ff.has_sr = false;
+ } else if (ff.has_aload) {
+ ff.unmap_ce_srst();
+
+ log("Replacing %s.%s (%s): ALOAD=%s, AD=%s, D=%s, Q=%s\n",
+ log_id(module), log_id(cell), log_id(cell->type),
+ log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_d), log_signal(ff.sig_q));
+
+ initvals.remove_init(ff.sig_q);
+
+ Wire *new_d = module->addWire(NEW_ID, ff.width);
+ Wire *new_q = module->addWire(NEW_ID, ff.width);
+
+ if (ff.pol_aload) {
+ if (!ff.is_fine) {
+ module->addMux(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, ff.sig_q);
+ module->addMux(NEW_ID, ff.sig_d, ff.sig_ad, ff.sig_aload, new_d);
+ } else {
+ module->addMuxGate(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, ff.sig_q);
+ module->addMuxGate(NEW_ID, ff.sig_d, ff.sig_ad, ff.sig_aload, new_d);
+ }
+ } else {
+ if (!ff.is_fine) {
+ module->addMux(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, ff.sig_q);
+ module->addMux(NEW_ID, ff.sig_ad, ff.sig_d, ff.sig_aload, new_d);
+ } else {
+ module->addMuxGate(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, ff.sig_q);
+ module->addMuxGate(NEW_ID, ff.sig_ad, ff.sig_d, ff.sig_aload, new_d);
+ }
+ }
+
+ ff.sig_d = new_d;
+ ff.sig_q = new_q;
+ ff.has_aload = false;
} else if (ff.has_arst) {
- ff.unmap_srst(module);
+ ff.unmap_srst();
log("Replacing %s.%s (%s): ARST=%s, D=%s, Q=%s\n",
log_id(module), log_id(cell), log_id(cell->type),
@@ -154,9 +182,12 @@ struct Async2syncPass : public Pass {
ff.sig_q = new_q;
ff.has_arst = false;
ff.has_srst = true;
+ ff.ce_over_srst = false;
ff.val_srst = ff.val_arst;
ff.sig_srst = ff.sig_arst;
ff.pol_srst = ff.pol_arst;
+ } else {
+ continue;
}
}
else
@@ -164,25 +195,25 @@ struct Async2syncPass : public Pass {
// Latch.
log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n",
log_id(module), log_id(cell), log_id(cell->type),
- log_signal(ff.sig_en), log_signal(ff.sig_d), log_signal(ff.sig_q));
+ log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q));
initvals.remove_init(ff.sig_q);
Wire *new_q = module->addWire(NEW_ID, ff.width);
Wire *new_d;
- if (ff.has_d) {
+ if (ff.has_aload) {
new_d = module->addWire(NEW_ID, ff.width);
- if (ff.pol_en) {
+ if (ff.pol_aload) {
if (!ff.is_fine)
- module->addMux(NEW_ID, new_q, ff.sig_d, ff.sig_en, new_d);
+ module->addMux(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, new_d);
else
- module->addMuxGate(NEW_ID, new_q, ff.sig_d, ff.sig_en, new_d);
+ module->addMuxGate(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, new_d);
} else {
if (!ff.is_fine)
- module->addMux(NEW_ID, ff.sig_d, new_q, ff.sig_en, new_d);
+ module->addMux(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, new_d);
else
- module->addMuxGate(NEW_ID, ff.sig_d, new_q, ff.sig_en, new_d);
+ module->addMuxGate(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, new_d);
}
} else {
new_d = new_q;
@@ -231,15 +262,12 @@ struct Async2syncPass : public Pass {
ff.sig_d = new_d;
ff.sig_q = new_q;
- ff.has_en = false;
+ ff.has_aload = false;
ff.has_arst = false;
ff.has_sr = false;
- ff.has_d = true;
+ ff.has_gclk = true;
}
-
- IdString name = cell->name;
- module->remove(cell);
- ff.emit(module, name);
+ ff.emit();
}
}
}
diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc
index 062083972..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;
@@ -148,12 +155,36 @@ struct Clk2fflogicPass : public Pass {
if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
FfData ff(&initvals, cell);
- if (ff.has_d && !ff.has_clk && !ff.has_en) {
+ if (ff.has_gclk) {
// Already a $ff or $_FF_ cell.
continue;
}
- Wire *past_q = module->addWire(NEW_ID, ff.width);
+ if (ff.has_clk) {
+ log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n",
+ log_id(module), log_id(cell), log_id(cell->type),
+ log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q));
+ } else if (ff.has_aload) {
+ log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n",
+ log_id(module), log_id(cell), log_id(cell->type),
+ log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q));
+ } else {
+ // $sr.
+ log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n",
+ log_id(module), log_id(cell), log_id(cell->type),
+ log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q));
+ }
+
+ ff.remove();
+
+ // 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 {
@@ -163,9 +194,9 @@ struct Clk2fflogicPass : public Pass {
initvals.set_init(past_q, ff.val_init);
if (ff.has_clk) {
- ff.unmap_ce_srst(module);
+ 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)
@@ -173,10 +204,6 @@ struct Clk2fflogicPass : public Pass {
else
module->addFfGate(NEW_ID, ff.sig_clk, past_clk);
- log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n",
- log_id(module), log_id(cell), log_id(cell->type),
- log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q));
-
SigSpec clock_edge_pattern;
if (ff.pol_clk) {
@@ -189,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
@@ -202,30 +229,22 @@ struct Clk2fflogicPass : public Pass {
qval = module->Mux(NEW_ID, past_q, past_d, clock_edge);
else
qval = module->MuxGate(NEW_ID, past_q, past_d, clock_edge);
- } else if (ff.has_d) {
-
- log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n",
- log_id(module), log_id(cell), log_id(cell->type),
- log_signal(ff.sig_en), log_signal(ff.sig_d), log_signal(ff.sig_q));
+ } else {
+ qval = past_q;
+ }
- SigSpec sig_en = wrap_async_control(module, ff.sig_en, ff.pol_en);
+ if (ff.has_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, past_q, ff.sig_d, sig_en);
+ qval = module->Mux(NEW_ID, qval, ff.sig_ad, sig_aload);
else
- qval = module->MuxGate(NEW_ID, past_q, ff.sig_d, sig_en);
- } else {
-
- log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n",
- log_id(module), log_id(cell), log_id(cell->type),
- log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q));
-
- qval = past_q;
+ qval = module->MuxGate(NEW_ID, qval, ff.sig_ad, sig_aload);
}
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);
@@ -236,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
@@ -244,10 +264,6 @@ struct Clk2fflogicPass : public Pass {
} else {
module->connect(ff.sig_q, qval);
}
-
- initvals.remove_init(ff.sig_q);
- module->remove(cell);
- continue;
}
}
}
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 49a0fad77..ff98a6e36 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) {
@@ -655,8 +697,9 @@ struct abc_output_filter
};
void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::string script_file, std::string exe_file,
- std::vector<std::string> &liberty_files, std::string constr_file, bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str,
- bool keepff, std::string delay_target, std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode,
+ std::vector<std::string> &liberty_files, std::vector<std::string> &genlib_files, std::string constr_file,
+ bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str, bool keepff, std::string delay_target,
+ std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode,
const std::vector<RTLIL::Cell*> &cells, bool show_tempdir, bool sop_mode, bool abc_dress)
{
module = current_module;
@@ -666,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 != "$")
{
@@ -675,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);
@@ -690,12 +759,22 @@ 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())
@@ -710,8 +789,11 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
std::string abc_script = stringf("read_blif %s/input.blif; ", tempdir_name.c_str());
- if (!liberty_files.empty()) {
- for (std::string liberty_file : liberty_files) abc_script += stringf("read_lib -w %s; ", liberty_file.c_str());
+ if (!liberty_files.empty() || !genlib_files.empty()) {
+ for (std::string liberty_file : liberty_files)
+ abc_script += stringf("read_lib -w %s; ", liberty_file.c_str());
+ for (std::string liberty_file : genlib_files)
+ abc_script += stringf("read_library %s; ", liberty_file.c_str());
if (!constr_file.empty())
abc_script += stringf("read_constr -v %s; ", constr_file.c_str());
} else
@@ -739,7 +821,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
abc_script += fast_mode ? ABC_FAST_COMMAND_LUT : ABC_COMMAND_LUT;
if (all_luts_cost_same && !fast_mode)
abc_script += "; lutpack {S}";
- } else if (!liberty_files.empty())
+ } else if (!liberty_files.empty() || !genlib_files.empty())
abc_script += constr_file.empty() ? (fast_mode ? ABC_FAST_COMMAND_LIB : ABC_COMMAND_LIB) : (fast_mode ? ABC_FAST_COMMAND_CTR : ABC_COMMAND_CTR);
else if (sop_mode)
abc_script += fast_mode ? ABC_FAST_COMMAND_SOP : ABC_COMMAND_SOP;
@@ -753,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))
@@ -785,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);
@@ -807,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());
@@ -913,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))
@@ -1020,7 +1113,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin
if (ifs.fail())
log_error("Can't open ABC output file `%s'.\n", buffer.c_str());
- bool builtin_lib = liberty_files.empty();
+ bool builtin_lib = liberty_files.empty() && genlib_files.empty();
RTLIL::Design *mapped_design = new RTLIL::Design;
parse_blif(mapped_design, ifs, builtin_lib ? ID(DFF) : ID(_dff_), false, sop_mode);
@@ -1039,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)
@@ -1145,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;
}
@@ -1176,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;
}
- cell->setPort(ID::C, clk_sig);
+ 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;
+ }
+ 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;
}
@@ -1225,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;
@@ -1302,10 +1428,10 @@ struct AbcPass : public Pass {
log("\n");
log(" if no -script parameter is given, the following scripts are used:\n");
log("\n");
- log(" for -liberty without -constr:\n");
+ log(" for -liberty/-genlib without -constr:\n");
log("%s\n", fold_abc_cmd(ABC_COMMAND_LIB).c_str());
log("\n");
- log(" for -liberty with -constr:\n");
+ log(" for -liberty/-genlib with -constr:\n");
log("%s\n", fold_abc_cmd(ABC_COMMAND_CTR).c_str());
log("\n");
log(" for -lut/-luts (only one LUT size):\n");
@@ -1324,10 +1450,10 @@ struct AbcPass : public Pass {
log(" use different default scripts that are slightly faster (at the cost\n");
log(" of output quality):\n");
log("\n");
- log(" for -liberty without -constr:\n");
+ log(" for -liberty/-genlib without -constr:\n");
log("%s\n", fold_abc_cmd(ABC_FAST_COMMAND_LIB).c_str());
log("\n");
- log(" for -liberty with -constr:\n");
+ log(" for -liberty/-genlib with -constr:\n");
log("%s\n", fold_abc_cmd(ABC_FAST_COMMAND_CTR).c_str());
log("\n");
log(" for -lut/-luts:\n");
@@ -1343,8 +1469,13 @@ struct AbcPass : public Pass {
log(" generate netlists for the specified cell library (using the liberty\n");
log(" file format).\n");
log("\n");
+ log(" -genlib <file>\n");
+ log(" generate netlists for the specified cell library (using the SIS Genlib\n");
+ log(" file format).\n");
+ log("\n");
log(" -constr <file>\n");
- log(" pass this file with timing constraints to ABC. use with -liberty.\n");
+ log(" pass this file with timing constraints to ABC.\n");
+ log(" use with -liberty/-genlib.\n");
log("\n");
log(" a constr file contains two lines:\n");
log(" set_driving_cell <cell_name>\n");
@@ -1390,7 +1521,7 @@ struct AbcPass : public Pass {
log("\n");
// log(" -mux4, -mux8, -mux16\n");
// log(" try to extract 4-input, 8-input, and/or 16-input muxes\n");
- // log(" (ignored when used with -liberty or -lut)\n");
+ // log(" (ignored when used with -liberty/-genlib or -lut)\n");
// log("\n");
log(" -g type1,type2,...\n");
log(" Map to the specified list of gate types. Supported gates types are:\n");
@@ -1446,7 +1577,7 @@ struct AbcPass : public Pass {
log(" preserve naming by an equivalence check between the original and post-ABC\n");
log(" netlists (experimental).\n");
log("\n");
- log("When neither -liberty nor -lut is used, the Yosys standard cell library is\n");
+ log("When no target cell library is specified the Yosys standard cell library is\n");
log("loaded into ABC before the ABC script is executed.\n");
log("\n");
log("Note that this is a logic optimization pass within Yosys that is calling ABC\n");
@@ -1473,7 +1604,7 @@ struct AbcPass : public Pass {
std::string exe_file = yosys_abc_executable;
std::string script_file, default_liberty_file, constr_file, clk_str;
- std::vector<std::string> liberty_files;
+ std::vector<std::string> liberty_files, genlib_files;
std::string delay_target, sop_inputs, sop_products, lutin_shared = "-S 1";
bool fast_mode = false, dff_mode = false, keepff = false, cleanup = true;
bool show_tempdir = false, sop_mode = false;
@@ -1556,6 +1687,10 @@ struct AbcPass : public Pass {
liberty_files.push_back(args[++argidx]);
continue;
}
+ if (arg == "-genlib" && argidx+1 < args.size()) {
+ genlib_files.push_back(args[++argidx]);
+ continue;
+ }
if (arg == "-constr" && argidx+1 < args.size()) {
constr_file = args[++argidx];
continue;
@@ -1645,7 +1780,8 @@ struct AbcPass : public Pass {
}
extra_args(args, argidx, design);
- if (liberty_files.empty() && !default_liberty_file.empty()) liberty_files.push_back(default_liberty_file);
+ if (genlib_files.empty() && liberty_files.empty() && !default_liberty_file.empty())
+ liberty_files.push_back(default_liberty_file);
rewrite_filename(script_file);
if (!script_file.empty() && !is_absolute_path(script_file) && script_file[0] != '+')
@@ -1655,6 +1791,11 @@ struct AbcPass : public Pass {
if (!liberty_files[i].empty() && !is_absolute_path(liberty_files[i]))
liberty_files[i] = std::string(pwd) + "/" + liberty_files[i];
}
+ for (int i = 0; i < GetSize(genlib_files); i++) {
+ rewrite_filename(genlib_files[i]);
+ if (!genlib_files[i].empty() && !is_absolute_path(genlib_files[i]))
+ genlib_files[i] = std::string(pwd) + "/" + genlib_files[i];
+ }
rewrite_filename(constr_file);
if (!constr_file.empty() && !is_absolute_path(constr_file))
constr_file = std::string(pwd) + "/" + constr_file;
@@ -1818,10 +1959,10 @@ struct AbcPass : public Pass {
}
}
- if (!lut_costs.empty() && !liberty_files.empty())
- log_cmd_error("Got -lut and -liberty! These two options are exclusive.\n");
- if (!constr_file.empty() && liberty_files.empty())
- log_cmd_error("Got -constr but no -liberty!\n");
+ if (!lut_costs.empty() && !(liberty_files.empty() && genlib_files.empty()))
+ log_cmd_error("Got -lut and -liberty/-genlib! These two options are exclusive.\n");
+ if (!constr_file.empty() && (liberty_files.empty() && genlib_files.empty()))
+ log_cmd_error("Got -constr but no -liberty/-genlib!\n");
if (enabled_gates.empty()) {
enabled_gates.insert("AND");
@@ -1851,7 +1992,7 @@ struct AbcPass : public Pass {
initvals.set(&assign_map, mod);
if (!dff_mode || !clk_str.empty()) {
- abc_module(design, mod, script_file, exe_file, liberty_files, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff,
+ abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff,
delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode, abc_dress);
continue;
}
@@ -1859,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)
{
@@ -1893,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);
@@ -1979,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;
@@ -1987,16 +2139,22 @@ 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));
- abc_module(design, mod, script_file, exe_file, liberty_files, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$",
+ 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..fe0802d70 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
{
diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc
index 7a6959971..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)
@@ -648,40 +662,38 @@ void prep_delays(RTLIL::Design *design, bool dff_mode)
auto inst_module = design->module(cell->type);
log_assert(inst_module);
- auto &t = timing.at(cell->type).required;
- for (auto &conn : cell->connections_) {
- auto port_wire = inst_module->wire(conn.first);
+ for (auto &i : timing.at(cell->type).required) {
+ auto port_wire = inst_module->wire(i.first.name);
if (!port_wire)
log_error("Port %s in cell %s (type %s) from module %s does not actually exist",
- log_id(conn.first), log_id(cell), log_id(cell->type), log_id(module));
- if (!port_wire->port_input)
- continue;
- if (conn.second.is_fully_const())
+ log_id(i.first.name), log_id(cell), log_id(cell->type), log_id(module));
+ log_assert(port_wire->port_input);
+
+ auto d = i.second.first;
+ if (d == 0)
continue;
- SigSpec O = module->addWire(NEW_ID, GetSize(conn.second));
- for (int i = 0; i < GetSize(conn.second); i++) {
- auto d = t.at(TimingInfo::NameBit(conn.first,i), 0);
- if (d == 0)
- continue;
+ auto offset = i.first.offset;
+ auto O = module->addWire(NEW_ID);
+ auto rhs = cell->getPort(i.first.name);
#ifndef NDEBUG
- if (ys_debug(1)) {
- static std::set<std::tuple<IdString,IdString,int>> seen;
- if (seen.emplace(cell->type, conn.first, i).second) log("%s.%s[%d] abc9_required = %d\n",
- log_id(cell->type), log_id(conn.first), i, d);
- }
+ if (ys_debug(1)) {
+ static pool<std::pair<IdString,TimingInfo::NameBit>> seen;
+ if (seen.emplace(cell->type, i.first).second) log("%s.%s[%d] abc9_required = %d\n",
+ log_id(cell->type), log_id(i.first.name), offset, d);
+ }
#endif
- auto r = box_cache.insert(d);
- if (r.second) {
- r.first->second = delay_module->derive(design, {{ID::DELAY, d}});
- log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY="));
- }
- auto box = module->addCell(NEW_ID, r.first->second);
- box->setPort(ID::I, conn.second[i]);
- box->setPort(ID::O, O[i]);
- conn.second[i] = O[i];
+ auto r = box_cache.insert(d);
+ if (r.second) {
+ r.first->second = delay_module->derive(design, {{ID::DELAY, d}});
+ log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY="));
}
+ auto box = module->addCell(NEW_ID, r.first->second);
+ box->setPort(ID::I, rhs[offset]);
+ box->setPort(ID::O, O);
+ rhs[offset] = O;
+ cell->setPort(i.first.name, rhs);
}
}
}
@@ -1006,16 +1018,16 @@ void prep_box(RTLIL::Design *design)
log_assert(GetSize(wire) == 1);
auto it = t.find(TimingInfo::NameBit(port_name,0));
if (it == t.end())
- // Assume no connectivity if no setup time
- ss << "-";
+ // Assume that no setup time means zero
+ ss << 0;
else {
- ss << it->second;
+ ss << it->second.first;
#ifndef NDEBUG
if (ys_debug(1)) {
static std::set<std::pair<IdString,IdString>> seen;
if (seen.emplace(module->name, port_name).second) log("%s.%s abc9_required = %d\n", log_id(module),
- log_id(port_name), it->second);
+ log_id(port_name), it->second.first);
}
#endif
}
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/dfflegalize.cc b/passes/techmap/dfflegalize.cc
index c1e7e557d..1d99caa3a 100644
--- a/passes/techmap/dfflegalize.cc
+++ b/passes/techmap/dfflegalize.cc
@@ -20,6 +20,7 @@
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include "kernel/ffinit.h"
+#include "kernel/ff.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@@ -27,38 +28,42 @@ PRIVATE_NAMESPACE_BEGIN
enum FfType {
FF_DFF,
FF_DFFE,
- FF_ADFF0,
- FF_ADFF1,
- FF_ADFFE0,
- FF_ADFFE1,
+ FF_ADFF,
+ FF_ADFFE,
+ FF_ALDFF,
+ FF_ALDFFE,
FF_DFFSR,
FF_DFFSRE,
- FF_SDFF0,
- FF_SDFF1,
- FF_SDFFE0,
- FF_SDFFE1,
- FF_SDFFCE0,
- FF_SDFFCE1,
+ FF_SDFF,
+ FF_SDFFE,
+ FF_SDFFCE,
+ FF_RLATCH,
FF_SR,
FF_DLATCH,
- FF_ADLATCH0,
- FF_ADLATCH1,
+ FF_ADLATCH,
FF_DLATCHSR,
NUM_FFTYPES,
};
enum FfNeg {
- NEG_R = 0x1,
- NEG_S = 0x2,
- NEG_E = 0x4,
- NEG_C = 0x8,
- NUM_NEG = 0x10,
+ NEG_CE = 0x1,
+ NEG_R = 0x2,
+ NEG_S = 0x4,
+ NEG_L = 0x8,
+ NEG_C = 0x10,
+ NUM_NEG = 0x20,
};
enum FfInit {
INIT_X = 0x1,
INIT_0 = 0x2,
INIT_1 = 0x4,
+ INIT_X_R0 = 0x10,
+ INIT_0_R0 = 0x20,
+ INIT_1_R0 = 0x40,
+ INIT_X_R1 = 0x100,
+ INIT_0_R1 = 0x200,
+ INIT_1_R1 = 0x400,
};
struct DffLegalizePass : public Pass {
@@ -101,6 +106,8 @@ struct DffLegalizePass : public Pass {
log("- $_DFFE_[NP][NP]_\n");
log("- $_DFF_[NP][NP][01]_\n");
log("- $_DFFE_[NP][NP][01][NP]_\n");
+ log("- $_ALDFF_[NP][NP]_\n");
+ log("- $_ALDFFE_[NP][NP][NP]_\n");
log("- $_DFFSR_[NP][NP][NP]_\n");
log("- $_DFFSRE_[NP][NP][NP][NP]_\n");
log("- $_SDFF_[NP][NP][01]_\n");
@@ -151,18 +158,30 @@ struct DffLegalizePass : public Pass {
int supported_cells[NUM_FFTYPES];
// Aggregated for all *dff* cells.
int supported_dff;
+ // Aggregated for all *dffe* cells.
+ int supported_dffe;
// Aggregated for all dffsr* cells.
int supported_dffsr;
- // Aggregated for all adff* cells.
- int supported_adff0;
- int supported_adff1;
+ // Aggregated for all aldff cells.
+ int supported_aldff;
+ // Aggregated for all aldffe cells.
+ int supported_aldffe;
+ // Aggregated for all adff* cells and trivial emulations.
+ int supported_adff;
+ // Aggregated for all adffe* cells and trivial emulations.
+ int supported_adffe;
// Aggregated for all sdff* cells.
- int supported_sdff0;
- int supported_sdff1;
+ int supported_sdff;
// Aggregated for all ways to obtain a SR latch.
int supported_sr;
+ int supported_sr_plain;
// Aggregated for all *dlatch* cells.
int supported_dlatch;
+ int supported_dlatch_plain;
+ // Aggregated for all ways to obtain an R latch.
+ int supported_rlatch;
+ // Aggregated for all adlatch cells and trivial emulations.
+ int supported_adlatch;
int mince;
int minsrst;
@@ -179,736 +198,794 @@ struct DffLegalizePass : public Pass {
res |= INIT_1;
if (mask & INIT_1)
res |= INIT_0;
+ if (mask & INIT_X_R0)
+ res |= INIT_X_R1;
+ if (mask & INIT_0_R0)
+ res |= INIT_1_R1;
+ if (mask & INIT_1_R0)
+ res |= INIT_0_R1;
+ if (mask & INIT_X_R1)
+ res |= INIT_X_R0;
+ if (mask & INIT_0_R1)
+ res |= INIT_1_R0;
+ if (mask & INIT_1_R1)
+ res |= INIT_0_R0;
return res;
}
- void handle_ff(Cell *cell) {
- std::string type_str = cell->type.str();
-
- FfType ff_type;
- int ff_neg = 0;
- SigSpec sig_d;
- SigSpec sig_q;
- SigSpec sig_c;
- SigSpec sig_e;
- SigSpec sig_r;
- SigSpec sig_s;
- bool has_srst = false;
-
- if (cell->hasPort(ID::D))
- sig_d = cell->getPort(ID::D);
- if (cell->hasPort(ID::Q))
- sig_q = cell->getPort(ID::Q);
- if (cell->hasPort(ID::C))
- sig_c = cell->getPort(ID::C);
- if (cell->hasPort(ID::E))
- sig_e = cell->getPort(ID::E);
- if (cell->hasPort(ID::R))
- sig_r = cell->getPort(ID::R);
- if (cell->hasPort(ID::S))
- sig_s = cell->getPort(ID::S);
-
- if (type_str.substr(0, 5) == "$_SR_") {
- ff_type = FF_SR;
- if (type_str[5] == 'N')
- ff_neg |= NEG_S;
- if (type_str[6] == 'N')
- ff_neg |= NEG_R;
- } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 8) {
- ff_type = FF_DFF;
- if (type_str[6] == 'N')
- ff_neg |= NEG_C;
- } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 10) {
- ff_type = FF_DFFE;
- if (type_str[7] == 'N')
- ff_neg |= NEG_C;
- if (type_str[8] == 'N')
- ff_neg |= NEG_E;
- } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 10) {
- ff_type = type_str[8] == '1' ? FF_ADFF1 : FF_ADFF0;
- if (type_str[6] == 'N')
- ff_neg |= NEG_C;
- if (type_str[7] == 'N')
- ff_neg |= NEG_R;
- } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 12) {
- ff_type = type_str[9] == '1' ? FF_ADFFE1 : FF_ADFFE0;
- if (type_str[7] == 'N')
- ff_neg |= NEG_C;
- if (type_str[8] == 'N')
- ff_neg |= NEG_R;
- if (type_str[10] == 'N')
- ff_neg |= NEG_E;
- } else if (type_str.substr(0, 8) == "$_DFFSR_" && type_str.size() == 12) {
- ff_type = FF_DFFSR;
- if (type_str[8] == 'N')
- ff_neg |= NEG_C;
- if (type_str[9] == 'N')
- ff_neg |= NEG_S;
- if (type_str[10] == 'N')
- ff_neg |= NEG_R;
- } else if (type_str.substr(0, 9) == "$_DFFSRE_" && type_str.size() == 14) {
- ff_type = FF_DFFSRE;
- if (type_str[9] == 'N')
- ff_neg |= NEG_C;
- if (type_str[10] == 'N')
- ff_neg |= NEG_S;
- if (type_str[11] == 'N')
- ff_neg |= NEG_R;
- if (type_str[12] == 'N')
- ff_neg |= NEG_E;
- } else if (type_str.substr(0, 7) == "$_SDFF_" && type_str.size() == 11) {
- ff_type = type_str[9] == '1' ? FF_SDFF1 : FF_SDFF0;
- if (type_str[7] == 'N')
- ff_neg |= NEG_C;
- if (type_str[8] == 'N')
- ff_neg |= NEG_R;
- has_srst = true;
- } else if (type_str.substr(0, 8) == "$_SDFFE_" && type_str.size() == 13) {
- ff_type = type_str[10] == '1' ? FF_SDFFE1 : FF_SDFFE0;
- if (type_str[8] == 'N')
- ff_neg |= NEG_C;
- if (type_str[9] == 'N')
- ff_neg |= NEG_R;
- if (type_str[11] == 'N')
- ff_neg |= NEG_E;
- has_srst = true;
- } else if (type_str.substr(0, 9) == "$_SDFFCE_" && type_str.size() == 14) {
- ff_type = type_str[11] == '1' ? FF_SDFFCE1 : FF_SDFFCE0;
- if (type_str[9] == 'N')
- ff_neg |= NEG_C;
- if (type_str[10] == 'N')
- ff_neg |= NEG_R;
- if (type_str[12] == 'N')
- ff_neg |= NEG_E;
- has_srst = true;
- } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 11) {
- ff_type = FF_DLATCH;
- if (type_str[9] == 'N')
- ff_neg |= NEG_E;
- } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 13) {
- ff_type = type_str[11] == '1' ? FF_ADLATCH1 : FF_ADLATCH0;
- if (type_str[9] == 'N')
- ff_neg |= NEG_E;
- if (type_str[10] == 'N')
- ff_neg |= NEG_R;
- } else if (type_str.substr(0, 11) == "$_DLATCHSR_" && type_str.size() == 15) {
- ff_type = FF_DLATCHSR;
- if (type_str[11] == 'N')
- ff_neg |= NEG_E;
- if (type_str[12] == 'N')
- ff_neg |= NEG_S;
- if (type_str[13] == 'N')
- ff_neg |= NEG_R;
+ int get_ff_type(const FfData &ff) {
+ if (ff.has_clk) {
+ if (ff.has_sr) {
+ return ff.has_ce ? FF_DFFSRE : FF_DFFSR;
+ } else if (ff.has_arst) {
+ return ff.has_ce ? FF_ADFFE : FF_ADFF;
+ } else if (ff.has_aload) {
+ return ff.has_ce ? FF_ALDFFE : FF_ALDFF;
+ } else if (ff.has_srst) {
+ if (ff.has_ce)
+ return ff.ce_over_srst ? FF_SDFFCE : FF_SDFFE;
+ else
+ return FF_SDFF;
+ } else {
+ return ff.has_ce ? FF_DFFE : FF_DFF;
+ }
} else {
- log_warning("Ignoring unknown ff type %s [%s.%s].\n", log_id(cell->type), log_id(cell->module->name), log_id(cell->name));
- return;
+ if (ff.has_aload) {
+ if (ff.has_sr)
+ return FF_DLATCHSR;
+ else if (ff.has_arst)
+ return FF_ADLATCH;
+ else
+ return FF_DLATCH;
+ } else {
+ if (ff.has_sr) {
+ return FF_SR;
+ } else if (ff.has_arst) {
+ return FF_RLATCH;
+ } else {
+ log_assert(0);
+ return 0;
+ }
+ }
+ }
+ }
+
+ int get_initmask(FfData &ff) {
+ int res = 0;
+ if (ff.val_init[0] == State::S0)
+ res = INIT_0;
+ else if (ff.val_init[0] == State::S1)
+ res = INIT_1;
+ else
+ res = INIT_X;
+ if (ff.has_arst) {
+ if (ff.val_arst[0] == State::S0)
+ res <<= 4;
+ else if (ff.val_arst[0] == State::S1)
+ res <<= 8;
+ } else if (ff.has_srst) {
+ if (ff.val_srst[0] == State::S0)
+ res <<= 4;
+ else if (ff.val_srst[0] == State::S1)
+ res <<= 8;
+ }
+ return res;
+ }
+
+ void fail_ff(const FfData &ff, const char *reason) {
+ log_error("FF %s.%s (type %s) cannot be legalized: %s\n", log_id(ff.module->name), log_id(ff.cell->name), log_id(ff.cell->type), reason);
+ }
+
+ bool try_flip(FfData &ff, int supported_mask) {
+ int initmask = get_initmask(ff);
+ if (supported_mask & initmask)
+ return true;
+ if (supported_mask & flip_initmask(initmask)) {
+ ff.flip_bits({0});
+ return true;
}
+ return false;
+ }
- State initval = initvals(sig_q[0]);
-
- FfInit initmask = INIT_X;
- if (initval == State::S0)
- initmask = INIT_0;
- else if (initval == State::S1)
- initmask = INIT_1;
- const char *reason;
+ void emulate_split_init_arst(FfData &ff) {
+ ff.remove();
- bool kill_ce = mince && GetSize(sig_c) && GetSize(sig_e) && sig_e[0].wire && ce_used[sig_e[0]] < mince;
- bool kill_srst = minsrst && has_srst && sig_r[0].wire && srst_used[sig_r[0]] < minsrst;
+ FfData ff_dff(ff.module, &initvals, NEW_ID);
+ ff_dff.width = ff.width;
+ ff_dff.has_aload = ff.has_aload;
+ ff_dff.sig_aload = ff.sig_aload;
+ ff_dff.pol_aload = ff.pol_aload;
+ ff_dff.sig_ad = ff.sig_ad;
+ ff_dff.has_clk = ff.has_clk;
+ ff_dff.sig_clk = ff.sig_clk;
+ ff_dff.pol_clk = ff.pol_clk;
+ ff_dff.sig_d = ff.sig_d;
+ ff_dff.has_ce = ff.has_ce;
+ ff_dff.sig_ce = ff.sig_ce;
+ ff_dff.pol_ce = ff.pol_ce;
+ ff_dff.sig_q = ff.module->addWire(NEW_ID, ff.width);
+ ff_dff.val_init = ff.val_init;
+ ff_dff.is_fine = ff.is_fine;
- while (!(supported_cells[ff_type] & initmask) || kill_ce || kill_srst) {
- // Well, cell is not directly supported. Decide how to deal with it.
+ FfData ff_adff(ff.module, &initvals, NEW_ID);
+ ff_adff.width = ff.width;
+ ff_adff.has_aload = ff.has_aload;
+ ff_adff.sig_aload = ff.sig_aload;
+ ff_adff.pol_aload = ff.pol_aload;
+ ff_adff.sig_ad = ff.sig_ad;
+ ff_adff.has_clk = ff.has_clk;
+ ff_adff.sig_clk = ff.sig_clk;
+ ff_adff.pol_clk = ff.pol_clk;
+ ff_adff.sig_d = ff.sig_d;
+ ff_adff.has_ce = ff.has_ce;
+ ff_adff.sig_ce = ff.sig_ce;
+ ff_adff.pol_ce = ff.pol_ce;
+ ff_adff.sig_q = ff.module->addWire(NEW_ID, ff.width);
+ ff_adff.val_init = Const(State::Sx, ff.width);
+ ff_adff.has_arst = true;
+ ff_adff.sig_arst = ff.sig_arst;
+ ff_adff.pol_arst = ff.pol_arst;
+ ff_adff.val_arst = ff.val_arst;
+ ff_adff.is_fine = ff.is_fine;
- if (ff_type == FF_DFF || ff_type == FF_DFFE) {
- if (kill_ce) {
- ff_type = FF_DFF;
- goto unmap_enable;
- }
- if (!(supported_dff & initmask)) {
- // This init value is not supported at all...
- if (supported_dff & flip_initmask(initmask)) {
- // The other one is, though. Negate D, Q, and init.
-flip_dqi:
- if (initval == State::S0) {
- initval = State::S1;
- initmask = INIT_1;
- } else if (initval == State::S1) {
- initval = State::S0;
- initmask = INIT_0;
- }
- if (ff_type != FF_SR)
- sig_d = cell->module->NotGate(NEW_ID, sig_d[0]);
- SigBit new_q = SigSpec(cell->module->addWire(NEW_ID))[0];
- cell->module->addNotGate(NEW_ID, new_q, sig_q[0]);
- initvals.remove_init(sig_q[0]);
- initvals.set_init(new_q, initval);
- sig_q = new_q;
- continue;
- }
- if (!supported_dff)
- reason = "dffs are not supported";
- else
- reason = "initialized dffs are not supported";
- goto error;
- }
+ FfData ff_sel(ff.module, &initvals, NEW_ID);
+ ff_sel.width = 1;
+ ff_sel.sig_q = ff.module->addWire(NEW_ID);
+ ff_sel.has_arst = true;
+ ff_sel.sig_arst = ff.sig_arst;
+ ff_sel.pol_arst = ff.pol_arst;
+ ff_sel.val_arst = State::S1;
+ ff_sel.val_init = State::S0;
+ ff_sel.is_fine = ff.is_fine;
- // Some DFF is supported with this init val. Just pick a type.
- if (ff_type == FF_DFF) {
- // Try adding a set or reset pin.
- for (auto new_type: {FF_SDFF0, FF_SDFF1, FF_ADFF0, FF_ADFF1})
- if (supported_cells[new_type] & initmask) {
- ff_type = new_type;
- sig_r = State::S0;
- goto cell_ok;
- }
- // Try adding both.
- if (supported_cells[FF_DFFSR] & initmask) {
- ff_type = FF_DFFSR;
- sig_r = State::S0;
- sig_s = State::S0;
- break;
- }
- // Nope. Will need to add enable and go the DFFE route.
- sig_e = State::S1;
- if (supported_cells[FF_DFFE] & initmask) {
- ff_type = FF_DFFE;
- break;
- }
- }
- // Try adding a set or reset pin.
- for (auto new_type: {FF_SDFFE0, FF_SDFFE1, FF_SDFFCE0, FF_SDFFCE1, FF_ADFFE0, FF_ADFFE1})
- if (supported_cells[new_type] & initmask) {
- ff_type = new_type;
- sig_r = State::S0;
- goto cell_ok;
- }
- // Try adding both.
- if (supported_cells[FF_DFFSRE] & initmask) {
- ff_type = FF_DFFSRE;
- sig_r = State::S0;
- sig_s = State::S0;
- break;
- }
+ if (ff.is_fine)
+ ff.module->addMuxGate(NEW_ID, ff_dff.sig_q, ff_adff.sig_q, ff_sel.sig_q, ff.sig_q);
+ else
+ ff.module->addMux(NEW_ID, ff_dff.sig_q, ff_adff.sig_q, ff_sel.sig_q, ff.sig_q);
+
+ legalize_ff(ff_dff);
+ legalize_ff(ff_adff);
+ legalize_ff(ff_sel);
+ }
- // Seems that no DFFs with enable are supported.
- // The enable input needs to be unmapped.
- // This should not be reached if we started from plain DFF.
- log_assert(ff_type == FF_DFFE);
- ff_type = FF_DFF;
-unmap_enable:
- if (ff_neg & NEG_E)
- sig_d = cell->module->MuxGate(NEW_ID, sig_d[0], sig_q[0], sig_e[0]);
+ void emulate_split_set_clr(FfData &ff) {
+ // No native DFFSR. However, if we can conjure
+ // a SR latch and ADFF, it can still be emulated.
+ int initmask = get_initmask(ff);
+ int flipmask = flip_initmask(initmask);
+ bool init_clr = true;
+ bool init_set = true;
+ State initsel = State::Sx;
+ int supported_arst = ff.has_clk ? supported_adff : supported_adlatch;
+ bool init_clr_ok = (supported_arst & initmask << 4) || (supported_arst & flipmask << 8);
+ bool init_set_ok = (supported_arst & initmask << 8) || (supported_arst & flipmask << 4);
+ if (init_clr_ok && init_set_ok && supported_sr) {
+ // OK
+ } else if (init_clr_ok && (supported_sr & INIT_0)) {
+ init_set = false;
+ initsel = State::S0;
+ } else if (init_set_ok && (supported_sr & INIT_1)) {
+ init_clr = false;
+ initsel = State::S1;
+ } else if (init_clr_ok && (supported_sr & INIT_1)) {
+ init_set = false;
+ initsel = State::S0;
+ } else if (init_set_ok && (supported_sr & INIT_0)) {
+ init_clr = false;
+ initsel = State::S1;
+ } else {
+ if (ff.has_clk) {
+ if (!supported_dffsr)
+ fail_ff(ff, "dffs with async set and reset are not supported");
else
- sig_d = cell->module->MuxGate(NEW_ID, sig_q[0], sig_d[0], sig_e[0]);
- ff_neg &= ~NEG_E;
- sig_e = SigSpec();
- kill_ce = false;
- // Now try again as plain DFF.
- continue;
- } else if (ff_type == FF_ADFF0 || ff_type == FF_ADFF1 || ff_type == FF_ADFFE0 || ff_type == FF_ADFFE1) {
- bool has_set = ff_type == FF_ADFF1 || ff_type == FF_ADFFE1;
- bool has_en = ff_type == FF_ADFFE0 || ff_type == FF_ADFFE1;
- if (kill_ce) {
- ff_type = has_set ? FF_ADFF1 : FF_ADFF0;
- goto unmap_enable;
- }
- if (!has_en && (supported_cells[has_set ? FF_ADFFE1 : FF_ADFFE0] & initmask)) {
- // Just add enable.
- sig_e = State::S1;
- ff_type = has_set ? FF_ADFFE1 : FF_ADFFE0;
- break;
- }
- if (supported_cells[has_en ? FF_DFFSRE : FF_DFFSR] & initmask) {
-adff_to_dffsr:
- // Throw in a set/reset, retry in DFFSR/DFFSRE branch.
- if (has_set) {
- sig_s = sig_r;
- sig_r = State::S0;
- if (ff_neg & NEG_R) {
- ff_neg &= ~NEG_R;
- ff_neg |= NEG_S;
- }
- } else {
- sig_s = State::S0;
- }
- if (has_en)
- ff_type = FF_DFFSRE;
- else
- ff_type = FF_DFFSR;
- continue;
- }
- if (has_en && (supported_cells[has_set ? FF_ADFF1 : FF_ADFF0] & initmask)) {
- // Unmap enable.
- ff_type = has_set ? FF_ADFF1 : FF_ADFF0;
- goto unmap_enable;
- }
- if (supported_dffsr & initmask) {
- goto adff_to_dffsr;
- }
- log_assert(!((has_set ? supported_adff1 : supported_adff0) & initmask));
- // Alright, so this particular combination of initval and
- // resetval is not natively supported. First, try flipping
- // them both to see whether this helps.
- int flipmask = flip_initmask(initmask);
- if ((has_set ? supported_adff0 : supported_adff1) & flipmask) {
- // Checks out, do it.
- ff_type = has_en ? (has_set ? FF_ADFFE0 : FF_ADFFE1) : (has_set ? FF_ADFF0 : FF_ADFF1);
- goto flip_dqi;
- }
+ fail_ff(ff, "initialized dffs with async set and reset are not supported");
+ } else {
+ if (!supported_cells[FF_DLATCHSR])
+ fail_ff(ff, "dlatch with async set and reset are not supported");
+ else
+ fail_ff(ff, "initialized dlatch with async set and reset are not supported");
+ }
+ }
- if (!supported_adff0 && !supported_adff1) {
- reason = "dffs with async set or reset are not supported";
- goto error;
- }
- if (!(supported_dff & ~INIT_X)) {
- reason = "initialized dffs are not supported";
- goto error;
- }
- // If we got here, initialized dff is supported, but not this
- // particular reset+init combination (nor its negation).
- // The only hope left is breaking down to adff + dff + dlatch + mux.
- if (!(supported_dlatch & ~INIT_X)) {
- reason = "unsupported initial value and async reset value combination";
- goto error;
- }
+ // If we have to unmap enable anyway, do it before breakdown.
+ if (ff.has_ce && !supported_cells[FF_ADFFE])
+ ff.unmap_ce();
- // If we have to unmap enable anyway, do it before breakdown.
- if (has_en && !supported_cells[FF_ADFFE0] && !supported_cells[FF_ADFFE1]) {
- ff_type = has_set ? FF_ADFF1 : FF_ADFF0;
- goto unmap_enable;
- }
+ log_warning("Emulating async set + reset with several FFs and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name));
- log_warning("Emulating mismatched async reset and init with several FFs and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name));
- initvals.remove_init(sig_q[0]);
- Wire *adff_q = cell->module->addWire(NEW_ID);
- Wire *dff_q = cell->module->addWire(NEW_ID);
- Wire *sel_q = cell->module->addWire(NEW_ID);
- initvals.set_init(SigBit(dff_q, 0), initval);
- initvals.set_init(SigBit(sel_q, 0), State::S0);
- Cell *cell_dff;
- Cell *cell_adff;
- Cell *cell_sel;
- if (!has_en) {
- cell_dff = cell->module->addDffGate(NEW_ID, sig_c, sig_d, dff_q, !(ff_neg & NEG_C));
- cell_adff = cell->module->addAdffGate(NEW_ID, sig_c, sig_r, sig_d, adff_q, has_set, !(ff_neg & NEG_C), !(ff_neg & NEG_R));
- } else {
- cell_dff = cell->module->addDffeGate(NEW_ID, sig_c, sig_e, sig_d, dff_q, !(ff_neg & NEG_C), !(ff_neg & NEG_E));
- cell_adff = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_r, sig_d, adff_q, has_set, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_R));
- }
- cell_sel = cell->module->addDlatchGate(NEW_ID, sig_r, State::S1, sel_q, !(ff_neg & NEG_R));
- cell->module->addMuxGate(NEW_ID, dff_q, adff_q, sel_q, sig_q);
-
- // Bye, cell.
- cell->module->remove(cell);
- handle_ff(cell_dff);
- handle_ff(cell_adff);
- handle_ff(cell_sel);
- return;
- } else if (ff_type == FF_DFFSR || ff_type == FF_DFFSRE) {
- if (kill_ce) {
- ff_type = FF_DFFSR;
- goto unmap_enable;
- }
- // First, see if mapping/unmapping enable will help.
- if (supported_cells[FF_DFFSRE] & initmask) {
- ff_type = FF_DFFSRE;
- sig_e = State::S1;
- break;
- }
- if (supported_cells[FF_DFFSR] & initmask) {
- ff_type = FF_DFFSR;
- goto unmap_enable;
- }
- if (supported_dffsr & flip_initmask(initmask)) {
-flip_dqisr:;
- log_warning("Flipping D/Q/init and inserting set/reset fixup to handle init value on %s.%s [%s]\n", log_id(cell->module->name), log_id(cell->name), log_id(cell->type));
- SigSpec new_r;
- bool neg_r = (ff_neg & NEG_R);
- bool neg_s = (ff_neg & NEG_S);
- if (!(ff_neg & NEG_S)) {
- if (!(ff_neg & NEG_R))
- new_r = cell->module->AndnotGate(NEW_ID, sig_s, sig_r);
- else
- new_r = cell->module->AndGate(NEW_ID, sig_s, sig_r);
- } else {
- if (!(ff_neg & NEG_R))
- new_r = cell->module->OrGate(NEW_ID, sig_s, sig_r);
- else
- new_r = cell->module->OrnotGate(NEW_ID, sig_s, sig_r);
- }
- ff_neg &= ~(NEG_R | NEG_S);
- if (neg_r)
- ff_neg |= NEG_S;
- if (neg_s)
- ff_neg |= NEG_R;
- sig_s = sig_r;
- sig_r = new_r;
- goto flip_dqi;
- }
- // No native DFFSR. However, if we can conjure
- // a SR latch and ADFF, it can still be emulated.
- int flipmask = flip_initmask(initmask);
- bool init0 = true;
- bool init1 = true;
- State initsel = State::Sx;
- if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && ((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && supported_sr) {
- // OK
- } else if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && (supported_sr & INIT_0)) {
- init1 = false;
- initsel = State::S0;
- } else if (((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && (supported_sr & INIT_1)) {
- init0 = false;
- initsel = State::S1;
- } else if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && (supported_sr & INIT_1)) {
- init1 = false;
- initsel = State::S0;
- } else if (((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && (supported_sr & INIT_0)) {
- init0 = false;
- initsel = State::S1;
- } else {
- if (!supported_dffsr)
- reason = "dffs with async set and reset are not supported";
- else
- reason = "initialized dffs with async set and reset are not supported";
- goto error;
- }
+ log_assert(ff.width == 1);
+ ff.remove();
- // If we have to unmap enable anyway, do it before breakdown.
- if (ff_type == FF_DFFSRE && !supported_cells[FF_ADFFE0] && !supported_cells[FF_ADFFE1]) {
- ff_type = FF_DFFSR;
- goto unmap_enable;
- }
+ FfData ff_clr(ff.module, &initvals, NEW_ID);
+ ff_clr.width = ff.width;
+ ff_clr.has_aload = ff.has_aload;
+ ff_clr.sig_aload = ff.sig_aload;
+ ff_clr.pol_aload = ff.pol_aload;
+ ff_clr.sig_ad = ff.sig_ad;
+ ff_clr.has_clk = ff.has_clk;
+ ff_clr.sig_clk = ff.sig_clk;
+ ff_clr.pol_clk = ff.pol_clk;
+ ff_clr.sig_d = ff.sig_d;
+ ff_clr.has_ce = ff.has_ce;
+ ff_clr.sig_ce = ff.sig_ce;
+ ff_clr.pol_ce = ff.pol_ce;
+ ff_clr.has_arst = true;
+ ff_clr.sig_arst = ff.sig_clr;
+ ff_clr.pol_arst = ff.pol_clr;
+ ff_clr.val_arst = Const(State::S0, ff.width);
+ ff_clr.sig_q = ff.module->addWire(NEW_ID, ff.width);
+ ff_clr.val_init = init_clr ? ff.val_init : Const(State::Sx, ff.width);
+ ff_clr.is_fine = ff.is_fine;
- log_warning("Emulating async set + reset with several FFs and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name));
- initvals.remove_init(sig_q[0]);
- Wire *adff0_q = cell->module->addWire(NEW_ID);
- Wire *adff1_q = cell->module->addWire(NEW_ID);
- Wire *sel_q = cell->module->addWire(NEW_ID);
- if (init0)
- initvals.set_init(SigBit(adff0_q, 0), initval);
- if (init1)
- initvals.set_init(SigBit(adff1_q, 0), initval);
- initvals.set_init(SigBit(sel_q, 0), initsel);
- Cell *cell_adff0;
- Cell *cell_adff1;
- Cell *cell_sel;
- if (ff_type == FF_DFFSR) {
- cell_adff0 = cell->module->addAdffGate(NEW_ID, sig_c, sig_r, sig_d, adff0_q, false, !(ff_neg & NEG_C), !(ff_neg & NEG_R));
- cell_adff1 = cell->module->addAdffGate(NEW_ID, sig_c, sig_s, sig_d, adff1_q, true, !(ff_neg & NEG_C), !(ff_neg & NEG_S));
- } else {
- cell_adff0 = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_r, sig_d, adff0_q, false, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_R));
- cell_adff1 = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_s, sig_d, adff1_q, true, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_S));
- }
- cell_sel = cell->module->addSrGate(NEW_ID, sig_s, sig_r, sel_q, !(ff_neg & NEG_S), !(ff_neg & NEG_R));
- cell->module->addMuxGate(NEW_ID, adff0_q, adff1_q, sel_q, sig_q);
-
- // Bye, cell.
- cell->module->remove(cell);
- handle_ff(cell_adff0);
- handle_ff(cell_adff1);
- handle_ff(cell_sel);
+ FfData ff_set(ff.module, &initvals, NEW_ID);
+ ff_set.width = ff.width;
+ ff_set.has_aload = ff.has_aload;
+ ff_set.sig_aload = ff.sig_aload;
+ ff_set.pol_aload = ff.pol_aload;
+ ff_set.sig_ad = ff.sig_ad;
+ ff_set.has_clk = ff.has_clk;
+ ff_set.sig_clk = ff.sig_clk;
+ ff_set.pol_clk = ff.pol_clk;
+ ff_set.sig_d = ff.sig_d;
+ ff_set.has_ce = ff.has_ce;
+ ff_set.sig_ce = ff.sig_ce;
+ ff_set.pol_ce = ff.pol_ce;
+ ff_set.has_arst = true;
+ ff_set.sig_arst = ff.sig_set;
+ ff_set.pol_arst = ff.pol_set;
+ ff_set.val_arst = Const(State::S1, ff.width);
+ ff_set.sig_q = ff.module->addWire(NEW_ID, ff.width);
+ ff_set.val_init = init_set ? ff.val_init : Const(State::Sx, ff.width);
+ ff_set.is_fine = ff.is_fine;
+
+ FfData ff_sel(ff.module, &initvals, NEW_ID);
+ ff_sel.width = ff.width;
+ ff_sel.has_sr = true;
+ ff_sel.pol_clr = ff.pol_clr;
+ ff_sel.pol_set = ff.pol_set;
+ ff_sel.sig_clr = ff.sig_clr;
+ ff_sel.sig_set = ff.sig_set;
+ ff_sel.sig_q = ff.module->addWire(NEW_ID, ff.width);
+ ff_sel.val_init = Const(initsel, ff.width);
+ ff_sel.is_fine = ff.is_fine;
+
+ if (!ff.is_fine)
+ ff.module->addMux(NEW_ID, ff_clr.sig_q, ff_set.sig_q, ff_sel.sig_q, ff.sig_q);
+ else
+ ff.module->addMuxGate(NEW_ID, ff_clr.sig_q, ff_set.sig_q, ff_sel.sig_q, ff.sig_q);
+
+ legalize_ff(ff_clr);
+ legalize_ff(ff_set);
+ legalize_ff(ff_sel);
+ }
+
+ void legalize_dff(FfData &ff) {
+ if (!try_flip(ff, supported_dff)) {
+ if (!supported_dff)
+ fail_ff(ff, "D flip-flops are not supported");
+ else
+ fail_ff(ff, "initialized D flip-flops are not supported");
+ }
+
+ int initmask = get_initmask(ff);
+ // Some DFF is supported with this init val. Just pick a type.
+ if (ff.has_ce && !(supported_dffe & initmask)) {
+ ff.unmap_ce();
+ }
+
+ if (!ff.has_ce) {
+ if (supported_cells[FF_DFF] & initmask) {
+ legalize_finish(ff);
return;
- } else if (ff_type == FF_SR) {
- if (supported_cells[FF_ADLATCH0] & initmask || supported_cells[FF_ADLATCH1] & flip_initmask(initmask)) {
- // Convert to ADLATCH0. May get converted to ADLATCH1.
- ff_type = FF_ADLATCH0;
- sig_e = sig_s;
- sig_d = State::S1;
- if (ff_neg & NEG_S) {
- ff_neg &= ~NEG_S;
- ff_neg |= NEG_E;
- }
- continue;
- } else if (supported_cells[FF_DLATCHSR] & initmask) {
- // Upgrade to DLATCHSR.
- ff_type = FF_DLATCHSR;
- sig_e = State::S0;
- sig_d = State::Sx;
- break;
- } else if (supported_dffsr & initmask) {
- // Upgrade to DFFSR. May get further upgraded to DFFSRE.
- ff_type = FF_DFFSR;
- sig_c = State::S0;
- sig_d = State::Sx;
- continue;
- } else if (supported_sr & flip_initmask(initmask)) {
- goto flip_dqisr;
- } else {
- if (!supported_sr)
- reason = "sr latches are not supported";
- else
- reason = "initialized sr latches are not supported";
- goto error;
- }
- } else if (ff_type == FF_DLATCH) {
- if (!(supported_dlatch & initmask)) {
- // This init value is not supported at all...
- if (supported_dlatch & flip_initmask(initmask))
- goto flip_dqi;
-
- if ((sig_d == State::S0 && (supported_adff0 & initmask)) ||
- (sig_d == State::S1 && (supported_adff1 & initmask)) ||
- (sig_d == State::S0 && (supported_adff1 & flip_initmask(initmask))) ||
- (sig_d == State::S1 && (supported_adff0 & flip_initmask(initmask)))
- ) {
- // Special case: const-D dlatch can be converted into adff with const clock.
- ff_type = (sig_d == State::S0) ? FF_ADFF0 : FF_ADFF1;
- if (ff_neg & NEG_E) {
- ff_neg &= ~NEG_E;
- ff_neg |= NEG_R;
- }
- sig_r = sig_e;
- sig_d = State::Sx;
- sig_c = State::S1;
- continue;
- }
+ }
+ // Try adding a set or reset pin.
+ if (supported_cells[FF_SDFF] & initmask) {
+ ff.add_dummy_srst();
+ legalize_finish(ff);
+ return;
+ }
+ if (supported_cells[FF_ADFF] & initmask) {
+ ff.add_dummy_arst();
+ legalize_finish(ff);
+ return;
+ }
+ // Try adding async load.
+ if (supported_cells[FF_ALDFF] & initmask) {
+ ff.add_dummy_aload();
+ legalize_finish(ff);
+ return;
+ }
+ // Try adding both.
+ if (supported_cells[FF_DFFSR] & initmask) {
+ ff.add_dummy_sr();
+ legalize_finish(ff);
+ return;
+ }
+ // Nope. Will need to add enable and go the DFFE route.
+ ff.add_dummy_ce();
+ }
+ if (supported_cells[FF_DFFE] & initmask) {
+ legalize_finish(ff);
+ return;
+ }
+ // Try adding a set or reset pin.
+ if (supported_cells[FF_SDFFCE] & initmask) {
+ ff.add_dummy_srst();
+ ff.ce_over_srst = true;
+ legalize_finish(ff);
+ return;
+ }
+ if (supported_cells[FF_SDFFE] & initmask) {
+ ff.add_dummy_srst();
+ legalize_finish(ff);
+ return;
+ }
+ if (supported_cells[FF_ADFFE] & initmask) {
+ ff.add_dummy_arst();
+ legalize_finish(ff);
+ return;
+ }
+ if (supported_cells[FF_ALDFFE] & initmask) {
+ ff.add_dummy_aload();
+ legalize_finish(ff);
+ return;
+ }
+ // Try adding both.
+ if (supported_cells[FF_DFFSRE] & initmask) {
+ ff.add_dummy_sr();
+ legalize_finish(ff);
+ return;
+ }
+ log_assert(0);
+ }
- if (!supported_dlatch)
- reason = "dlatch are not supported";
- else
- reason = "initialized dlatch are not supported";
- goto error;
- }
+ void legalize_sdffce(FfData &ff) {
+ if (!try_flip(ff, supported_cells[FF_SDFFCE] | supported_cells[FF_SDFFE])) {
+ ff.unmap_srst();
+ legalize_dff(ff);
+ return;
+ }
- // Some DLATCH is supported with this init val. Just pick a type.
- if (supported_cells[FF_ADLATCH0] & initmask) {
- ff_type = FF_ADLATCH0;
- sig_r = State::S0;
- break;
- }
- if (supported_cells[FF_ADLATCH1] & initmask) {
- ff_type = FF_ADLATCH1;
- sig_r = State::S0;
- break;
- }
- if (supported_cells[FF_DLATCHSR] & initmask) {
- ff_type = FF_DLATCHSR;
- sig_r = State::S0;
- sig_s = State::S0;
- break;
- }
+ int initmask = get_initmask(ff);
+ if (supported_cells[FF_SDFFCE] & initmask) {
+ // OK
+ } else if (supported_cells[FF_SDFFE] & initmask) {
+ ff.convert_ce_over_srst(false);
+ } else {
+ log_assert(0);
+ }
+ legalize_finish(ff);
+ }
+
+ void legalize_sdff(FfData &ff) {
+ if (!try_flip(ff, supported_sdff)) {
+ ff.unmap_srst();
+ legalize_dff(ff);
+ return;
+ }
+
+ int initmask = get_initmask(ff);
+ if (!ff.has_ce) {
+ if (supported_cells[FF_SDFF] & initmask) {
+ // OK
+ } else if (supported_cells[FF_SDFFE] & initmask) {
+ ff.add_dummy_ce();
+ } else if (supported_cells[FF_SDFFCE] & initmask) {
+ ff.add_dummy_ce();
+ ff.ce_over_srst = true;
+ } else {
log_assert(0);
- } else if (ff_type == FF_ADLATCH0 || ff_type == FF_ADLATCH1) {
- if (supported_cells[FF_DLATCHSR] & initmask) {
- if (ff_type == FF_ADLATCH1) {
- sig_s = sig_r;
- sig_r = State::S0;
- if (ff_neg & NEG_R) {
- ff_neg &= ~NEG_R;
- ff_neg |= NEG_S;
- }
- } else {
- sig_s = State::S0;
- }
- ff_type = FF_DLATCHSR;
- break;
- }
- FfType flip_type = ff_type == FF_ADLATCH0 ? FF_ADLATCH1 : FF_ADLATCH0;
- if ((supported_cells[flip_type] | supported_cells[FF_DLATCHSR]) & flip_initmask(initmask)) {
- ff_type = flip_type;
- goto flip_dqi;
- }
+ }
+ } else {
+ log_assert(!ff.ce_over_srst);
+ if (supported_cells[FF_SDFFE] & initmask) {
+ // OK
+ } else if (supported_cells[FF_SDFFCE] & initmask) {
+ ff.convert_ce_over_srst(true);
+ } else if (supported_cells[FF_SDFF] & initmask) {
+ ff.unmap_ce();
+ } else {
+ log_assert(0);
+ }
+ }
+ legalize_finish(ff);
+ }
- if (!supported_cells[FF_ADLATCH0] && !supported_cells[FF_ADLATCH1] && !supported_cells[FF_DLATCHSR]) {
- reason = "dlatch with async set or reset are not supported";
- goto error;
- }
- if (!(supported_dlatch & ~INIT_X)) {
- reason = "initialized dlatch are not supported";
- goto error;
- }
+ void legalize_adff(FfData &ff) {
+ if (!try_flip(ff, supported_adff)) {
+ if (!supported_adff)
+ fail_ff(ff, "dffs with async set or reset are not supported");
+ if (!(supported_dff & (INIT_0 | INIT_1)))
+ fail_ff(ff, "initialized dffs are not supported");
- if (!(supported_dlatch & ~INIT_X)) {
- reason = "initialized dlatch are not supported";
- goto error;
- }
- // If we got here, initialized dlatch is supported, but not this
- // particular reset+init combination (nor its negation).
- // The only hope left is breaking down to adff + dff + dlatch + mux.
-
- log_warning("Emulating mismatched async reset and init with several latches and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name));
- initvals.remove_init(sig_q[0]);
- Wire *adlatch_q = cell->module->addWire(NEW_ID);
- Wire *dlatch_q = cell->module->addWire(NEW_ID);
- Wire *sel_q = cell->module->addWire(NEW_ID);
- initvals.set_init(SigBit(dlatch_q, 0), initval);
- initvals.set_init(SigBit(sel_q, 0), State::S0);
- Cell *cell_dlatch;
- Cell *cell_adlatch;
- Cell *cell_sel;
- cell_dlatch = cell->module->addDlatchGate(NEW_ID, sig_e, sig_d, dlatch_q, !(ff_neg & NEG_E));
- cell_adlatch = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_r, sig_d, adlatch_q, ff_type == FF_ADLATCH1, !(ff_neg & NEG_E), !(ff_neg & NEG_R));
- cell_sel = cell->module->addDlatchGate(NEW_ID, sig_r, State::S1, sel_q, !(ff_neg & NEG_R));
- cell->module->addMuxGate(NEW_ID, dlatch_q, adlatch_q, sel_q, sig_q);
-
- // Bye, cell.
- cell->module->remove(cell);
- handle_ff(cell_dlatch);
- handle_ff(cell_adlatch);
- handle_ff(cell_sel);
+ // If we got here, initialized dff is supported, but not this
+ // particular reset+init combination (nor its negation).
+ // The only hope left is breaking down to adff + dff + dlatch + mux.
+
+ if (!((supported_rlatch) & (INIT_0_R1 | INIT_1_R0)))
+ fail_ff(ff, "unsupported initial value and async reset value combination");
+
+ // If we have to unmap enable anyway, do it before breakdown.
+ if (ff.has_ce && !supported_cells[FF_ADFFE])
+ ff.unmap_ce();
+
+ if (ff.cell)
+ log_warning("Emulating mismatched async reset and init with several FFs and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name));
+ emulate_split_init_arst(ff);
+ return;
+ }
+
+ int initmask = get_initmask(ff);
+ if (ff.has_ce && !(supported_adffe & initmask)) {
+ ff.unmap_ce();
+ }
+
+ if (!ff.has_ce) {
+ if (supported_cells[FF_ADFF] & initmask) {
+ legalize_finish(ff);
return;
- } else if (ff_type == FF_DLATCHSR) {
- if (supported_cells[FF_DLATCHSR] & flip_initmask(initmask)) {
- goto flip_dqisr;
- }
- // No native DFFSR. However, if we can conjure
- // a SR latch and ADFF, it can still be emulated.
- int flipmask = flip_initmask(initmask);
- bool init0 = true;
- bool init1 = true;
- State initsel = State::Sx;
- if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && ((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && supported_sr) {
- // OK
- } else if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && (supported_sr & INIT_0)) {
- init1 = false;
- initsel = State::S0;
- } else if (((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && (supported_sr & INIT_1)) {
- init0 = false;
- initsel = State::S1;
- } else if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && (supported_sr & INIT_1)) {
- init1 = false;
- initsel = State::S0;
- } else if (((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && (supported_sr & INIT_0)) {
- init0 = false;
- initsel = State::S1;
- } else {
- if (!supported_cells[FF_DLATCHSR])
- reason = "dlatch with async set and reset are not supported";
- else
- reason = "initialized dlatch with async set and reset are not supported";
- goto error;
- }
+ }
+ // Try converting to async load.
+ if (supported_cells[FF_ALDFF] & initmask) {
+ ff.arst_to_aload();
+ legalize_finish(ff);
+ return;
+ }
+ // Try convertint to SR.
+ if (supported_cells[FF_DFFSR] & initmask) {
+ ff.arst_to_sr();
+ legalize_finish(ff);
+ return;
+ }
+ ff.add_dummy_ce();
+ }
+ if (supported_cells[FF_ADFFE] & initmask) {
+ legalize_finish(ff);
+ return;
+ }
+ // Try converting to async load.
+ if (supported_cells[FF_ALDFFE] & initmask) {
+ ff.arst_to_aload();
+ legalize_finish(ff);
+ return;
+ }
+ // Try convertint to SR.
+ if (supported_cells[FF_DFFSRE] & initmask) {
+ ff.arst_to_sr();
+ legalize_finish(ff);
+ return;
+ }
+ log_assert(0);
+ }
+
+ void legalize_aldff(FfData &ff) {
+ if (!try_flip(ff, supported_aldff)) {
+ ff.aload_to_sr();
+ emulate_split_set_clr(ff);
+ return;
+ }
+
+ int initmask = get_initmask(ff);
+ if (ff.has_ce && !(supported_aldffe & initmask)) {
+ ff.unmap_ce();
+ }
- log_warning("Emulating async set + reset with several latches and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name));
- initvals.remove_init(sig_q[0]);
- Wire *adlatch0_q = cell->module->addWire(NEW_ID);
- Wire *adlatch1_q = cell->module->addWire(NEW_ID);
- Wire *sel_q = cell->module->addWire(NEW_ID);
- if (init0)
- initvals.set_init(SigBit(adlatch0_q, 0), initval);
- if (init1)
- initvals.set_init(SigBit(adlatch1_q, 0), initval);
- initvals.set_init(SigBit(sel_q, 0), initsel);
- Cell *cell_adlatch0;
- Cell *cell_adlatch1;
- Cell *cell_sel;
- cell_adlatch0 = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_r, sig_d, adlatch0_q, false, !(ff_neg & NEG_E), !(ff_neg & NEG_R));
- cell_adlatch1 = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_s, sig_d, adlatch1_q, true, !(ff_neg & NEG_E), !(ff_neg & NEG_S));
- cell_sel = cell->module->addSrGate(NEW_ID, sig_s, sig_r, sel_q, !(ff_neg & NEG_S), !(ff_neg & NEG_R));
- cell->module->addMuxGate(NEW_ID, adlatch0_q, adlatch1_q, sel_q, sig_q);
-
- // Bye, cell.
- cell->module->remove(cell);
- handle_ff(cell_adlatch0);
- handle_ff(cell_adlatch1);
- handle_ff(cell_sel);
+ if (!ff.has_ce) {
+ if (supported_cells[FF_ALDFF] & initmask) {
+ legalize_finish(ff);
return;
- } else if (ff_type == FF_SDFF0 || ff_type == FF_SDFF1 || ff_type == FF_SDFFE0 || ff_type == FF_SDFFE1 || ff_type == FF_SDFFCE0 || ff_type == FF_SDFFCE1) {
- bool has_set = ff_type == FF_SDFF1 || ff_type == FF_SDFFE1 || ff_type == FF_SDFFCE1;
- bool has_en = ff_type == FF_SDFFE0 || ff_type == FF_SDFFE1;
- bool has_ce = ff_type == FF_SDFFCE0 || ff_type == FF_SDFFCE1;
-
- if (has_en) {
- if (kill_ce || kill_srst) {
- ff_type = has_set ? FF_SDFF1 : FF_SDFF0;
- goto unmap_enable;
- }
- } else if (has_ce) {
- if (kill_ce || kill_srst)
- goto unmap_srst;
- } else {
- log_assert(!kill_ce);
- if (kill_srst)
- goto unmap_srst;
- }
+ }
+ if (supported_cells[FF_DFFSR] & initmask) {
+ ff.aload_to_sr();
+ legalize_finish(ff);
+ return;
+ }
+ ff.add_dummy_ce();
+ }
+ if (supported_cells[FF_ALDFFE] & initmask) {
+ legalize_finish(ff);
+ return;
+ }
+ if (supported_cells[FF_DFFSRE] & initmask) {
+ ff.aload_to_sr();
+ legalize_finish(ff);
+ return;
+ }
+ log_assert(0);
+ }
- if (!has_ce) {
- if (!has_en && (supported_cells[has_set ? FF_SDFFE1 : FF_SDFFE0] & initmask)) {
- // Just add enable.
- sig_e = State::S1;
- ff_type = has_set ? FF_SDFFE1 : FF_SDFFE0;
- break;
- }
- if (!has_en && (supported_cells[has_set ? FF_SDFFCE1 : FF_SDFFCE0] & initmask)) {
- // Just add enable.
- sig_e = State::S1;
- ff_type = has_set ? FF_SDFFCE1 : FF_SDFFCE0;
- break;
- }
- if (has_en && (supported_cells[has_set ? FF_SDFFCE1 : FF_SDFFCE0] & initmask)) {
- // Convert sdffe to sdffce
- if (!(ff_neg & NEG_E)) {
- if (!(ff_neg & NEG_R))
- sig_e = cell->module->OrGate(NEW_ID, sig_e, sig_r);
- else
- sig_e = cell->module->OrnotGate(NEW_ID, sig_e, sig_r);
- } else {
- if (!(ff_neg & NEG_R))
- sig_e = cell->module->AndnotGate(NEW_ID, sig_e, sig_r);
- else
- sig_e = cell->module->AndGate(NEW_ID, sig_e, sig_r);
- }
- ff_type = has_set ? FF_SDFFCE1 : FF_SDFFCE0;
- break;
- }
- if (has_en && (supported_cells[has_set ? FF_SDFF1 : FF_SDFF0] & initmask)) {
- // Unmap enable.
- ff_type = has_set ? FF_SDFF1 : FF_SDFF0;
- goto unmap_enable;
- }
- log_assert(!((has_set ? supported_sdff1 : supported_sdff0) & initmask));
- } else {
- if ((has_set ? supported_sdff1 : supported_sdff0) & initmask) {
- // Convert sdffce to sdffe, which may be further converted to sdff.
- if (!(ff_neg & NEG_R)) {
- if (!(ff_neg & NEG_E))
- sig_r = cell->module->AndGate(NEW_ID, sig_r, sig_e);
- else
- sig_r = cell->module->AndnotGate(NEW_ID, sig_r, sig_e);
- } else {
- if (!(ff_neg & NEG_E))
- sig_r = cell->module->OrnotGate(NEW_ID, sig_r, sig_e);
- else
- sig_r = cell->module->OrGate(NEW_ID, sig_r, sig_e);
- }
- ff_type = has_set ? FF_SDFFE1 : FF_SDFFE0;
- continue;
- }
+ void legalize_dffsr(FfData &ff) {
+ if (!try_flip(ff, supported_dffsr)) {
+ emulate_split_set_clr(ff);
+ return;
+ }
+
+ int initmask = get_initmask(ff);
+ if (ff.has_ce && !(supported_cells[FF_DFFSRE] & initmask)) {
+ ff.unmap_ce();
+ }
+
+ if (!ff.has_ce) {
+ if (supported_cells[FF_DFFSR] & initmask) {
+ legalize_finish(ff);
+ return;
+ }
+ ff.add_dummy_ce();
+ }
+
+ log_assert(supported_cells[FF_DFFSRE] & initmask);
+ legalize_finish(ff);
+ }
+
+ void legalize_dlatch(FfData &ff) {
+ if (!try_flip(ff, supported_dlatch)) {
+ if (!supported_dlatch)
+ fail_ff(ff, "D latches are not supported");
+ else
+ fail_ff(ff, "initialized D latches are not supported");
+ }
+
+ int initmask = get_initmask(ff);
+ // Some DLATCH is supported with this init val. Just pick a type.
+ if (supported_cells[FF_DLATCH] & initmask) {
+ legalize_finish(ff);
+ } else if (supported_cells[FF_ADLATCH] & initmask) {
+ ff.add_dummy_arst();
+ legalize_finish(ff);
+ } else if (supported_cells[FF_DLATCHSR] & initmask) {
+ ff.add_dummy_sr();
+ legalize_finish(ff);
+ } else if (supported_cells[FF_ALDFF] & initmask) {
+ ff.add_dummy_clk();
+ legalize_finish(ff);
+ } else if (supported_cells[FF_ALDFFE] & initmask) {
+ ff.add_dummy_clk();
+ ff.add_dummy_ce();
+ legalize_finish(ff);
+ } else if (supported_sr & initmask) {
+ ff.aload_to_sr();
+ legalize_sr(ff);
+ } else {
+ log_assert(0);
+ }
+ }
+
+ void legalize_adlatch(FfData &ff) {
+ if (!try_flip(ff, supported_adlatch)) {
+ if (!supported_adlatch)
+ fail_ff(ff, "D latches with async set or reset are not supported");
+ if (!(supported_dlatch & (INIT_0 | INIT_1)))
+ fail_ff(ff, "initialized D latches are not supported");
+
+ // If we got here, initialized dlatch is supported, but not this
+ // particular reset+init combination (nor its negation).
+ // The only hope left is breaking down to adlatch + dlatch + dlatch + mux.
+
+ if (ff.cell)
+ log_warning("Emulating mismatched async reset and init with several latches and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name));
+ ff.remove();
+
+ emulate_split_init_arst(ff);
+ return;
+ }
+ int initmask = get_initmask(ff);
+ if (supported_cells[FF_ADLATCH] & initmask) {
+ // OK
+ } else if (supported_cells[FF_DLATCHSR] & initmask) {
+ ff.arst_to_sr();
+ } else {
+ log_assert(0);
+ }
+ legalize_finish(ff);
+ }
+
+ void legalize_dlatchsr(FfData &ff) {
+ if (!try_flip(ff, supported_cells[FF_DLATCHSR])) {
+ emulate_split_set_clr(ff);
+ return;
+ }
+ legalize_finish(ff);
+ }
+
+ void legalize_rlatch(FfData &ff) {
+ if (!try_flip(ff, supported_rlatch)) {
+ if (!supported_dlatch)
+ fail_ff(ff, "D latches are not supported");
+ else
+ fail_ff(ff, "initialized D latches are not supported");
+ }
+
+ int initmask = get_initmask(ff);
+ if (((supported_dlatch_plain & 7) * 0x111) & initmask) {
+ ff.arst_to_aload();
+ legalize_dlatch(ff);
+ } else if (supported_sr & initmask) {
+ ff.arst_to_sr();
+ legalize_sr(ff);
+ } else if (supported_adff & initmask) {
+ ff.add_dummy_clk();
+ legalize_adff(ff);
+ } else {
+ log_assert(0);
+ }
+ }
+
+ void legalize_sr(FfData &ff) {
+ if (!try_flip(ff, supported_sr)) {
+ if (!supported_sr)
+ fail_ff(ff, "sr latches are not supported");
+ else
+ fail_ff(ff, "initialized sr latches are not supported");
+ }
+ int initmask = get_initmask(ff);
+ if (supported_cells[FF_SR] & initmask) {
+ // OK
+ } else if (supported_cells[FF_DLATCHSR] & initmask) {
+ // Upgrade to DLATCHSR.
+ ff.add_dummy_aload();
+ } else if (supported_cells[FF_DFFSR] & initmask) {
+ // Upgrade to DFFSR.
+ ff.add_dummy_clk();
+ } else if (supported_cells[FF_DFFSRE] & initmask) {
+ // Upgrade to DFFSRE.
+ ff.add_dummy_clk();
+ ff.add_dummy_ce();
+ } else if (supported_cells[FF_ADLATCH] & (initmask << 4)) {
+ ff.has_sr = false;
+ ff.has_aload = true;
+ ff.has_arst = true;
+ ff.pol_arst = ff.pol_clr;
+ ff.sig_arst = ff.sig_clr;
+ ff.sig_aload = ff.sig_set;
+ ff.pol_aload = ff.pol_set;
+ ff.sig_ad = State::S1;
+ ff.val_arst = State::S0;
+ } else if (supported_cells[FF_ADLATCH] & (flip_initmask(initmask) << 8)) {
+ ff.has_sr = false;
+ ff.has_aload = true;
+ ff.has_arst = true;
+ ff.pol_arst = ff.pol_clr;
+ ff.sig_arst = ff.sig_clr;
+ ff.sig_aload = ff.sig_set;
+ ff.pol_aload = ff.pol_set;
+ ff.sig_ad = State::S0;
+ ff.val_arst = State::S1;
+ ff.remove_init();
+ Wire *new_q = ff.module->addWire(NEW_ID);
+ if (ff.is_fine)
+ ff.module->addNotGate(NEW_ID, new_q, ff.sig_q);
+ else
+ ff.module->addNot(NEW_ID, new_q, ff.sig_q);
+ ff.sig_q = new_q;
+ if (ff.val_init == State::S0)
+ ff.val_init = State::S1;
+ else if (ff.val_init == State::S1)
+ ff.val_init = State::S0;
+ } else {
+ log_assert(0);
+ }
+ legalize_finish(ff);
+ }
+
+ void fixup_reset_x(FfData &ff, int supported) {
+ for (int i = 0; i < ff.width; i++) {
+ int mask;
+ if (ff.val_init[i] == State::S0)
+ mask = INIT_0;
+ else if (ff.val_init[i] == State::S1)
+ mask = INIT_1;
+ else
+ mask = INIT_X;
+ if (ff.has_arst) {
+ if (ff.val_arst[i] == State::Sx) {
+ if (!(supported & (mask << 8)))
+ ff.val_arst[i] = State::S0;
+ if (!(supported & (mask << 4)))
+ ff.val_arst[i] = State::S1;
}
- // Alright, so this particular combination of initval and
- // resetval is not natively supported. First, try flipping
- // them both to see whether this helps.
- if ((has_set ? supported_sdff0 : supported_sdff1) & flip_initmask(initmask)) {
- // Checks out, do it.
- ff_type = has_ce ? (has_set ? FF_SDFFCE0 : FF_SDFFCE1) : has_en ? (has_set ? FF_SDFFE0 : FF_SDFFE1) : (has_set ? FF_SDFF0 : FF_SDFF1);
- goto flip_dqi;
+ }
+ if (ff.has_srst) {
+ if (ff.val_srst[i] == State::Sx) {
+ if (!(supported & (mask << 8)))
+ ff.val_srst[i] = State::S0;
+ if (!(supported & (mask << 4)))
+ ff.val_srst[i] = State::S1;
}
+ }
+ }
+ }
- // Nope. No way to get SDFF* of the right kind, so unmap it.
- // For SDFFE, the enable has to be unmapped first.
- if (has_en) {
- ff_type = has_set ? FF_SDFF1 : FF_SDFF0;
- goto unmap_enable;
- }
-unmap_srst:
- if (has_ce)
- ff_type = FF_DFFE;
- else
- ff_type = FF_DFF;
- if (ff_neg & NEG_R)
- sig_d = cell->module->MuxGate(NEW_ID, has_set ? State::S1 : State::S0, sig_d[0], sig_r[0]);
+ void legalize_ff(FfData &ff) {
+ if (ff.has_gclk)
+ return;
+
+ // TODO: consider supporting coarse as well.
+ if (!ff.is_fine)
+ return;
+
+ if (mince && ff.has_ce && ff.sig_ce[0].wire && ce_used[ff.sig_ce[0]] < mince)
+ ff.unmap_ce();
+ if (minsrst && ff.has_srst && ff.sig_srst[0].wire && srst_used[ff.sig_srst[0]] < minsrst)
+ ff.unmap_srst();
+
+ if (ff.has_clk) {
+ if (ff.has_sr) {
+ legalize_dffsr(ff);
+ } else if (ff.has_aload) {
+ legalize_aldff(ff);
+ } else if (ff.has_arst) {
+ legalize_adff(ff);
+ } else if (ff.has_srst) {
+ if (ff.has_ce && ff.ce_over_srst)
+ legalize_sdffce(ff);
else
- sig_d = cell->module->MuxGate(NEW_ID, sig_d[0], has_set ? State::S1 : State::S0, sig_r[0]);
- ff_neg &= ~NEG_R;
- sig_r = SigSpec();
- kill_srst = false;
- continue;
+ legalize_sdff(ff);
+ } else {
+ legalize_dff(ff);
+ }
+ } else if (ff.has_aload) {
+ if (ff.has_sr) {
+ legalize_dlatchsr(ff);
+ } else if (ff.has_arst) {
+ legalize_adlatch(ff);
+ } else {
+ legalize_dlatch(ff);
+ }
+ } else {
+ if (ff.has_sr) {
+ legalize_sr(ff);
+ } else if (ff.has_arst) {
+ legalize_rlatch(ff);
} else {
log_assert(0);
}
}
-cell_ok:
+ }
+
+ void flip_pol(FfData &ff, SigSpec &sig, bool &pol) {
+ if (sig == State::S0) {
+ sig = State::S1;
+ } else if (sig == State::S1) {
+ sig = State::S0;
+ } else if (ff.is_fine) {
+ sig = ff.module->NotGate(NEW_ID, sig);
+ } else {
+ sig = ff.module->Not(NEW_ID, sig);
+ }
+ pol = !pol;
+ }
+ void legalize_finish(FfData &ff) {
+ int ff_type = get_ff_type(ff);
+ int initmask = get_initmask(ff);
+ log_assert(supported_cells[ff_type] & initmask);
+ int ff_neg = 0;
+ if (ff.has_sr) {
+ if (!ff.pol_clr)
+ ff_neg |= NEG_R;
+ if (!ff.pol_set)
+ ff_neg |= NEG_S;
+ }
+ if (ff.has_arst) {
+ if (!ff.pol_arst)
+ ff_neg |= NEG_R;
+ }
+ if (ff.has_srst) {
+ if (!ff.pol_srst)
+ ff_neg |= NEG_R;
+ }
+ if (ff.has_aload) {
+ if (!ff.pol_aload)
+ ff_neg |= NEG_L;
+ }
+ if (ff.has_clk) {
+ if (!ff.pol_clk)
+ ff_neg |= NEG_C;
+ }
+ if (ff.has_ce) {
+ if (!ff.pol_ce)
+ ff_neg |= NEG_CE;
+ }
if (!(supported_cells_neg[ff_type][ff_neg] & initmask)) {
// Cell is supported, but not with those polarities.
// Will need to add some inverters.
@@ -921,182 +998,27 @@ cell_ok:
if (supported_cells_neg[ff_type][ff_neg ^ xneg] & initmask)
break;
log_assert(xneg < NUM_NEG);
- if (xneg & NEG_R)
- sig_r = cell->module->NotGate(NEW_ID, sig_r[0]);
- if (xneg & NEG_S)
- sig_s = cell->module->NotGate(NEW_ID, sig_s[0]);
- if (xneg & NEG_E)
- sig_e = cell->module->NotGate(NEW_ID, sig_e[0]);
+ if (xneg & NEG_CE)
+ flip_pol(ff, ff.sig_ce, ff.pol_ce);
+ if (ff.has_sr) {
+ if (xneg & NEG_R)
+ flip_pol(ff, ff.sig_clr, ff.pol_clr);
+ if (xneg & NEG_S)
+ flip_pol(ff, ff.sig_set, ff.pol_set);
+ }
+ if (ff.has_arst && xneg & NEG_R)
+ flip_pol(ff, ff.sig_arst, ff.pol_arst);
+ if (ff.has_srst && xneg & NEG_R)
+ flip_pol(ff, ff.sig_srst, ff.pol_srst);
+ if (xneg & NEG_L)
+ flip_pol(ff, ff.sig_aload, ff.pol_aload);
if (xneg & NEG_C)
- sig_c = cell->module->NotGate(NEW_ID, sig_c[0]);
+ flip_pol(ff, ff.sig_clk, ff.pol_clk);
ff_neg ^= xneg;
}
- cell->unsetPort(ID::D);
- cell->unsetPort(ID::Q);
- cell->unsetPort(ID::C);
- cell->unsetPort(ID::E);
- cell->unsetPort(ID::S);
- cell->unsetPort(ID::R);
- switch (ff_type) {
- case FF_DFF:
- cell->type = IdString(stringf("$_DFF_%c_",
- (ff_neg & NEG_C) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- break;
- case FF_DFFE:
- cell->type = IdString(stringf("$_DFFE_%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::E, sig_e);
- break;
- case FF_ADFF0:
- case FF_ADFF1:
- cell->type = IdString(stringf("$_DFF_%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_ADFF1) ? '1' : '0'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_ADFFE0:
- case FF_ADFFE1:
- cell->type = IdString(stringf("$_DFFE_%c%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_ADFFE1) ? '1' : '0',
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_DFFSR:
- cell->type = IdString(stringf("$_DFFSR_%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_S) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::S, sig_s);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_DFFSRE:
- cell->type = IdString(stringf("$_DFFSRE_%c%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_S) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::S, sig_s);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_SDFF0:
- case FF_SDFF1:
- cell->type = IdString(stringf("$_SDFF_%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_SDFF1) ? '1' : '0'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_SDFFE0:
- case FF_SDFFE1:
- cell->type = IdString(stringf("$_SDFFE_%c%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_SDFFE1) ? '1' : '0',
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_SDFFCE0:
- case FF_SDFFCE1:
- cell->type = IdString(stringf("$_SDFFCE_%c%c%c%c_",
- (ff_neg & NEG_C) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_SDFFCE1) ? '1' : '0',
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::C, sig_c);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_DLATCH:
- cell->type = IdString(stringf("$_DLATCH_%c_",
- (ff_neg & NEG_E) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::E, sig_e);
- break;
- case FF_ADLATCH0:
- case FF_ADLATCH1:
- cell->type = IdString(stringf("$_DLATCH_%c%c%c_",
- (ff_neg & NEG_E) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P',
- (ff_type == FF_ADLATCH1) ? '1' : '0'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_DLATCHSR:
- cell->type = IdString(stringf("$_DLATCHSR_%c%c%c_",
- (ff_neg & NEG_E) ? 'N' : 'P',
- (ff_neg & NEG_S) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P'
- ));
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::E, sig_e);
- cell->setPort(ID::S, sig_s);
- cell->setPort(ID::R, sig_r);
- break;
- case FF_SR:
- cell->type = IdString(stringf("$_SR_%c%c_",
- (ff_neg & NEG_S) ? 'N' : 'P',
- (ff_neg & NEG_R) ? 'N' : 'P'
- ));
- cell->setPort(ID::Q, sig_q);
- cell->setPort(ID::S, sig_s);
- cell->setPort(ID::R, sig_r);
- break;
- default:
- log_assert(0);
- }
- return;
-
-error:
- log_error("FF %s.%s (type %s) cannot be legalized: %s\n", log_id(cell->module->name), log_id(cell->name), log_id(cell->type), reason);
+ fixup_reset_x(ff, supported_cells_neg[ff_type][ff_neg]);
+ ff.emit();
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
@@ -1118,79 +1040,83 @@ error:
if (args[argidx] == "-cell" && argidx + 2 < args.size()) {
std::string celltype = args[++argidx];
std::string inittype = args[++argidx];
- enum FfType ff_type[2] = {NUM_FFTYPES, NUM_FFTYPES};
+ enum FfType ff_type;
char pol_c = 0;
- char pol_e = 0;
+ char pol_l = 0;
char pol_s = 0;
char pol_r = 0;
+ char pol_ce = 0;
char srval = 0;
if (celltype.substr(0, 5) == "$_SR_" && celltype.size() == 8 && celltype[7] == '_') {
- ff_type[0] = FF_SR;
+ ff_type = FF_SR;
pol_s = celltype[5];
pol_r = celltype[6];
} else if (celltype.substr(0, 6) == "$_DFF_" && celltype.size() == 8 && celltype[7] == '_') {
- ff_type[0] = FF_DFF;
+ ff_type = FF_DFF;
pol_c = celltype[6];
} else if (celltype.substr(0, 7) == "$_DFFE_" && celltype.size() == 10 && celltype[9] == '_') {
- ff_type[0] = FF_DFFE;
+ ff_type = FF_DFFE;
pol_c = celltype[7];
- pol_e = celltype[8];
+ pol_ce = celltype[8];
} else if (celltype.substr(0, 6) == "$_DFF_" && celltype.size() == 10 && celltype[9] == '_') {
- ff_type[0] = FF_ADFF0;
- ff_type[1] = FF_ADFF1;
+ ff_type = FF_ADFF;
pol_c = celltype[6];
pol_r = celltype[7];
srval = celltype[8];
} else if (celltype.substr(0, 7) == "$_DFFE_" && celltype.size() == 12 && celltype[11] == '_') {
- ff_type[0] = FF_ADFFE0;
- ff_type[1] = FF_ADFFE1;
+ ff_type = FF_ADFFE;
pol_c = celltype[7];
pol_r = celltype[8];
srval = celltype[9];
- pol_e = celltype[10];
+ pol_ce = celltype[10];
+ } else if (celltype.substr(0, 8) == "$_ALDFF_" && celltype.size() == 11 && celltype[10] == '_') {
+ ff_type = FF_ALDFF;
+ pol_c = celltype[8];
+ pol_l = celltype[9];
+ } else if (celltype.substr(0, 9) == "$_ALDFFE_" && celltype.size() == 13 && celltype[12] == '_') {
+ ff_type = FF_ALDFFE;
+ pol_c = celltype[9];
+ pol_l = celltype[10];
+ pol_ce = celltype[11];
} else if (celltype.substr(0, 8) == "$_DFFSR_" && celltype.size() == 12 && celltype[11] == '_') {
- ff_type[0] = FF_DFFSR;
+ ff_type = FF_DFFSR;
pol_c = celltype[8];
pol_s = celltype[9];
pol_r = celltype[10];
} else if (celltype.substr(0, 9) == "$_DFFSRE_" && celltype.size() == 14 && celltype[13] == '_') {
- ff_type[0] = FF_DFFSRE;
+ ff_type = FF_DFFSRE;
pol_c = celltype[9];
pol_s = celltype[10];
pol_r = celltype[11];
- pol_e = celltype[12];
+ pol_ce = celltype[12];
} else if (celltype.substr(0, 7) == "$_SDFF_" && celltype.size() == 11 && celltype[10] == '_') {
- ff_type[0] = FF_SDFF0;
- ff_type[1] = FF_SDFF1;
+ ff_type = FF_SDFF;
pol_c = celltype[7];
pol_r = celltype[8];
srval = celltype[9];
} else if (celltype.substr(0, 8) == "$_SDFFE_" && celltype.size() == 13 && celltype[12] == '_') {
- ff_type[0] = FF_SDFFE0;
- ff_type[1] = FF_SDFFE1;
+ ff_type = FF_SDFFE;
pol_c = celltype[8];
pol_r = celltype[9];
srval = celltype[10];
- pol_e = celltype[11];
+ pol_ce = celltype[11];
} else if (celltype.substr(0, 9) == "$_SDFFCE_" && celltype.size() == 14 && celltype[13] == '_') {
- ff_type[0] = FF_SDFFCE0;
- ff_type[1] = FF_SDFFCE1;
+ ff_type = FF_SDFFCE;
pol_c = celltype[9];
pol_r = celltype[10];
srval = celltype[11];
- pol_e = celltype[12];
+ pol_ce = celltype[12];
} else if (celltype.substr(0, 9) == "$_DLATCH_" && celltype.size() == 11 && celltype[10] == '_') {
- ff_type[0] = FF_DLATCH;
- pol_e = celltype[9];
+ ff_type = FF_DLATCH;
+ pol_l = celltype[9];
} else if (celltype.substr(0, 9) == "$_DLATCH_" && celltype.size() == 13 && celltype[12] == '_') {
- ff_type[0] = FF_ADLATCH0;
- ff_type[1] = FF_ADLATCH1;
- pol_e = celltype[9];
+ ff_type = FF_ADLATCH;
+ pol_l = celltype[9];
pol_r = celltype[10];
srval = celltype[11];
} else if (celltype.substr(0, 11) == "$_DLATCHSR_" && celltype.size() == 15 && celltype[14] == '_') {
- ff_type[0] = FF_DLATCHSR;
- pol_e = celltype[11];
+ ff_type = FF_DLATCHSR;
+ pol_l = celltype[11];
pol_s = celltype[12];
pol_r = celltype[13];
} else {
@@ -1201,9 +1127,10 @@ unrecognized:
int match = 0;
for (auto pair : {
std::make_pair(pol_c, NEG_C),
- std::make_pair(pol_e, NEG_E),
+ std::make_pair(pol_l, NEG_L),
std::make_pair(pol_s, NEG_S),
std::make_pair(pol_r, NEG_R),
+ std::make_pair(pol_ce, NEG_CE),
}) {
if (pair.first == 'N') {
mask |= pair.second;
@@ -1214,40 +1141,33 @@ unrecognized:
goto unrecognized;
}
}
+ int initmask;
+ if (inittype == "x") {
+ initmask = 0x111;
+ } else if (inittype == "0") {
+ initmask = 0x333;
+ } else if (inittype == "1") {
+ initmask = 0x555;
+ } else if (inittype == "r") {
+ if (srval == 0)
+ log_error("init type r not valid for cell type %s.\n", celltype.c_str());
+ initmask = 0x537;
+ } else if (inittype == "01") {
+ initmask = 0x777;
+ } else {
+ log_error("unrecognized init type %s for cell type %s.\n", inittype.c_str(), celltype.c_str());
+ }
if (srval == '0') {
- ff_type[1] = NUM_FFTYPES;
+ initmask &= 0x0ff;
} else if (srval == '1') {
- ff_type[0] = NUM_FFTYPES;
+ initmask &= 0xf0f;
} else if (srval != 0 && srval != '?') {
goto unrecognized;
}
- for (int i = 0; i < 2; i++) {
- if (ff_type[i] == NUM_FFTYPES)
- continue;
- int initmask;
- if (inittype == "x") {
- initmask = INIT_X;
- } else if (inittype == "0") {
- initmask = INIT_X | INIT_0;
- } else if (inittype == "1") {
- initmask = INIT_X | INIT_1;
- } else if (inittype == "r") {
- if (srval == 0)
- log_error("init type r not valid for cell type %s.\n", celltype.c_str());
- if (i == 0)
- initmask = INIT_X | INIT_0;
- else
- initmask = INIT_X | INIT_1;
- } else if (inittype == "01") {
- initmask = INIT_X | INIT_0 | INIT_1;
- } else {
- log_error("unrecognized init type %s for cell type %s.\n", inittype.c_str(), celltype.c_str());
- }
- for (int neg = 0; neg < NUM_NEG; neg++)
- if ((neg & mask) == match)
- supported_cells_neg[ff_type[i]][neg] |= initmask;
- supported_cells[ff_type[i]] |= initmask;
- }
+ for (int neg = 0; neg < NUM_NEG; neg++)
+ if ((neg & mask) == match)
+ supported_cells_neg[ff_type][neg] |= initmask;
+ supported_cells[ff_type] |= initmask;
continue;
} else if (args[argidx] == "-mince" && argidx + 1 < args.size()) {
mince = atoi(args[++argidx].c_str());
@@ -1260,13 +1180,21 @@ unrecognized:
}
extra_args(args, argidx, design);
supported_dffsr = supported_cells[FF_DFFSR] | supported_cells[FF_DFFSRE];
- supported_adff0 = supported_cells[FF_ADFF0] | supported_cells[FF_ADFFE0] | supported_dffsr;
- supported_adff1 = supported_cells[FF_ADFF1] | supported_cells[FF_ADFFE1] | supported_dffsr;
- supported_sdff0 = supported_cells[FF_SDFF0] | supported_cells[FF_SDFFE0] | supported_cells[FF_SDFFCE0];
- supported_sdff1 = supported_cells[FF_SDFF1] | supported_cells[FF_SDFFE1] | supported_cells[FF_SDFFCE1];
- supported_dff = supported_cells[FF_DFF] | supported_cells[FF_DFFE] | supported_dffsr | supported_adff0 | supported_adff1 | supported_sdff0 | supported_sdff1;
- supported_sr = supported_dffsr | supported_cells[FF_DLATCHSR] | supported_cells[FF_SR] | supported_cells[FF_ADLATCH0] | flip_initmask(supported_cells[FF_ADLATCH1]);
- supported_dlatch = supported_cells[FF_DLATCH] | supported_cells[FF_ADLATCH0] | supported_cells[FF_ADLATCH1] | supported_cells[FF_DLATCHSR];
+ supported_aldff = supported_cells[FF_ALDFF] | supported_cells[FF_ALDFFE] | supported_dffsr;
+ supported_aldffe = supported_cells[FF_ALDFFE] | supported_cells[FF_DFFSRE];
+ supported_adff = supported_cells[FF_ADFF] | supported_cells[FF_ADFFE] | supported_dffsr | supported_aldff;
+ supported_adffe = supported_cells[FF_ADFFE] | supported_cells[FF_ALDFFE] | supported_cells[FF_DFFSRE];
+ supported_sdff = supported_cells[FF_SDFF] | supported_cells[FF_SDFFE] | supported_cells[FF_SDFFCE];
+ supported_dff = supported_cells[FF_DFF] | supported_cells[FF_DFFE] | supported_adff | supported_sdff;
+ supported_dffe = supported_cells[FF_DFFE] | supported_cells[FF_DFFSRE] | supported_cells[FF_ALDFFE] | supported_cells[FF_ADFFE] | supported_cells[FF_SDFFE] | supported_cells[FF_SDFFCE];
+ supported_sr_plain = supported_dffsr | supported_cells[FF_DLATCHSR] | supported_cells[FF_SR];
+ supported_sr = supported_sr_plain;
+ supported_sr |= (supported_cells[FF_ADLATCH] >> 4 & 7) * 0x111;
+ supported_sr |= (flip_initmask(supported_cells[FF_ADLATCH]) >> 4 & 7) * 0x111;
+ supported_dlatch_plain = supported_cells[FF_DLATCH] | supported_cells[FF_ADLATCH] | supported_cells[FF_DLATCHSR] | supported_cells[FF_ALDFF] | supported_cells[FF_ALDFFE];
+ supported_dlatch = supported_dlatch_plain | supported_sr_plain;
+ supported_rlatch = supported_adff | (supported_dlatch & 7) * 0x111;
+ supported_adlatch = supported_cells[FF_ADLATCH] | supported_cells[FF_DLATCHSR];
for (auto module : design->selected_modules())
{
@@ -1281,36 +1209,20 @@ unrecognized:
if (!RTLIL::builtin_ff_cell_types().count(cell->type))
continue;
- if (cell->hasPort(ID::C) && cell->hasPort(ID::E)) {
- SigSpec sig = cell->getPort(ID::E);
- // Do not count const enable signals.
- if (GetSize(sig) == 1 && sig[0].wire)
- ce_used[sig[0]]++;
- }
- if (cell->type.str().substr(0, 6) == "$_SDFF") {
- SigSpec sig = cell->getPort(ID::R);
- // Do not count const srst signals.
- if (GetSize(sig) == 1 && sig[0].wire)
- srst_used[sig[0]]++;
- }
+ FfData ff(&initvals, cell);
+ if (ff.has_ce && ff.sig_ce[0].wire)
+ ce_used[ff.sig_ce[0]] += ff.width;
+ if (ff.has_srst && ff.sig_srst[0].wire)
+ srst_used[ff.sig_srst[0]] += ff.width;
}
}
-
- // First gather FF cells, then iterate over them later.
- // We may need to split an FF into several cells.
- std::vector<Cell *> ff_cells;
-
for (auto cell : module->selected_cells())
{
- // Early exit for non-FFs.
if (!RTLIL::builtin_ff_cell_types().count(cell->type))
continue;
-
- ff_cells.push_back(cell);
+ FfData ff(&initvals, cell);
+ legalize_ff(ff);
}
-
- for (auto cell: ff_cells)
- handle_ff(cell);
}
sigmap.clear();
diff --git a/passes/techmap/dffunmap.cc b/passes/techmap/dffunmap.cc
index fb107ff75..7312015f1 100644
--- a/passes/techmap/dffunmap.cc
+++ b/passes/techmap/dffunmap.cc
@@ -84,21 +84,20 @@ struct DffunmapPass : public Pass {
continue;
if (ce_only) {
- if (!ff.has_en)
+ if (!ff.has_ce)
continue;
- ff.unmap_ce(mod);
+ ff.unmap_ce();
} else if (srst_only) {
if (!ff.has_srst)
continue;
- ff.unmap_srst(mod);
+ ff.unmap_srst();
} else {
- if (!ff.has_en && !ff.has_srst)
+ if (!ff.has_ce && !ff.has_srst)
continue;
- ff.unmap_ce_srst(mod);
+ ff.unmap_ce_srst();
}
- mod->remove(cell);
- ff.emit(mod, name);
+ ff.emit();
}
}
}
diff --git a/passes/techmap/extract_reduce.cc b/passes/techmap/extract_reduce.cc
index 07b4200cc..892e9a364 100644
--- a/passes/techmap/extract_reduce.cc
+++ b/passes/techmap/extract_reduce.cc
@@ -152,10 +152,10 @@ struct ExtractReducePass : public Pass
log_assert(y.size() == 1);
// Should only continue if there is one fanout back into a cell (not to a port)
- if (sig_to_sink[y[0]].size() != 1)
+ if (sig_to_sink[y].size() != 1 || port_sigs.count(y))
break;
- x = *sig_to_sink[y[0]].begin();
+ x = *sig_to_sink[y].begin();
}
sinks.insert(head_cell);
@@ -183,13 +183,15 @@ struct ExtractReducePass : public Pass
continue;
}
+ auto xy = sigmap(x->getPort(ID::Y));
+
//If this signal drives a port, add it to the sinks
//(even though it may not be the end of a chain)
- if(port_sigs.count(x) && !consumed_cells.count(x))
+ if(port_sigs.count(xy) && !consumed_cells.count(x))
sinks.insert(x);
//It's a match, search everything out from it
- auto& next = sig_to_sink[x];
+ auto& next = sig_to_sink[xy];
for(auto z : next)
next_loads.insert(z);
}
@@ -224,89 +226,60 @@ struct ExtractReducePass : public Pass
if(consumed_cells.count(head_cell))
continue;
- pool<Cell*> cur_supercell;
+ dict<SigBit, int> sources;
+ int inner_cells = 0;
std::deque<Cell*> bfs_queue = {head_cell};
while (bfs_queue.size())
{
Cell* x = bfs_queue.front();
bfs_queue.pop_front();
- cur_supercell.insert(x);
+ for (auto port: {ID::A, ID::B}) {
+ auto bit = sigmap(x->getPort(port)[0]);
- auto a = sigmap(x->getPort(ID::A));
- log_assert(a.size() == 1);
+ bool sink_single = sig_to_sink[bit].size() == 1 && !port_sigs.count(bit);
- // Must have only one sink unless we're going off chain
- // XXX: Check that it is indeed this node?
- if( allow_off_chain || (sig_to_sink[a[0]].size() + port_sigs.count(a[0]) == 1) )
- {
- Cell* cell_a = sig_to_driver[a[0]];
- if(cell_a && IsRightType(cell_a, gt))
- {
- // The cell here is the correct type, and it's definitely driving
- // this current cell.
- bfs_queue.push_back(cell_a);
- }
- }
+ Cell* drv = sig_to_driver[bit];
+ bool drv_ok = drv && drv->type == head_cell->type;
- auto b = sigmap(x->getPort(ID::B));
- log_assert(b.size() == 1);
-
- // Must have only one sink
- // XXX: Check that it is indeed this node?
- if( allow_off_chain || (sig_to_sink[b[0]].size() + port_sigs.count(b[0]) == 1) )
- {
- Cell* cell_b = sig_to_driver[b[0]];
- if(cell_b && IsRightType(cell_b, gt))
- {
- // The cell here is the correct type, and it's definitely driving only
- // this current cell.
- bfs_queue.push_back(cell_b);
+ if (drv_ok && (allow_off_chain || sink_single)) {
+ inner_cells++;
+ bfs_queue.push_back(drv);
+ } else {
+ sources[bit]++;
}
}
}
- log(" Cells:\n");
- for (auto x : cur_supercell)
- log(" %s\n", x->name.c_str());
-
- if (cur_supercell.size() > 1)
+ if (inner_cells)
{
// Worth it to create reduce cell
log(" Creating $reduce_* cell!\n");
- pool<SigBit> input_pool;
- pool<SigBit> input_pool_intermed;
- for (auto x : cur_supercell)
- {
- input_pool.insert(sigmap(x->getPort(ID::A))[0]);
- input_pool.insert(sigmap(x->getPort(ID::B))[0]);
- input_pool_intermed.insert(sigmap(x->getPort(ID::Y))[0]);
- }
- SigSpec input;
- for (auto b : input_pool)
- if (input_pool_intermed.count(b) == 0)
- input.append(b);
-
SigBit output = sigmap(head_cell->getPort(ID::Y)[0]);
- auto new_reduce_cell = module->addCell(NEW_ID,
- gt == GateType::And ? ID($reduce_and) :
- gt == GateType::Or ? ID($reduce_or) :
- gt == GateType::Xor ? ID($reduce_xor) : "");
- new_reduce_cell->setParam(ID::A_SIGNED, 0);
- new_reduce_cell->setParam(ID::A_WIDTH, input.size());
- new_reduce_cell->setParam(ID::Y_WIDTH, 1);
- new_reduce_cell->setPort(ID::A, input);
- new_reduce_cell->setPort(ID::Y, output);
-
- if(allow_off_chain)
- consumed_cells.insert(head_cell);
- else
- {
- for (auto x : cur_supercell)
- consumed_cells.insert(x);
+ SigSpec input;
+ for (auto it : sources) {
+ bool cond;
+ if (head_cell->type == ID($_XOR_))
+ cond = it.second & 1;
+ else
+ cond = it.second != 0;
+ if (cond)
+ input.append(it.first);
+ }
+
+ if (head_cell->type == ID($_AND_)) {
+ module->addReduceAnd(NEW_ID, input, output);
+ } else if (head_cell->type == ID($_OR_)) {
+ module->addReduceOr(NEW_ID, input, output);
+ } else if (head_cell->type == ID($_XOR_)) {
+ module->addReduceXor(NEW_ID, input, output);
+ } else {
+ log_assert(false);
}
+
+ consumed_cells.insert(head_cell);
}
}
}
diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc
index 616fee3f5..7e6df5d2c 100644
--- a/passes/techmap/flatten.cc
+++ b/passes/techmap/flatten.cc
@@ -77,7 +77,7 @@ struct FlattenWorker
{
bool ignore_wb = false;
- void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, std::vector<RTLIL::Cell*> &new_cells)
+ void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, SigMap &sigmap, std::vector<RTLIL::Cell*> &new_cells)
{
// Copy the contents of the flattened cell
@@ -165,7 +165,6 @@ struct FlattenWorker
for (auto bit : tpl_conn.first)
tpl_driven.insert(bit);
- SigMap sigmap(module);
for (auto &port_it : cell->connections())
{
IdString port_name = port_it.first;
@@ -218,6 +217,7 @@ struct FlattenWorker
log_id(module), log_id(cell), log_id(port_it.first), log_signal(new_conn.first), log_signal(new_conn.second));
module->connect(new_conn);
+ sigmap.add(new_conn.first, new_conn.second);
}
module->remove(cell);
@@ -228,6 +228,7 @@ struct FlattenWorker
if (!design->selected(module) || module->get_blackbox_attribute(ignore_wb))
return;
+ SigMap sigmap(module);
std::vector<RTLIL::Cell*> worklist = module->selected_cells();
while (!worklist.empty())
{
@@ -251,7 +252,7 @@ struct FlattenWorker
// If a design is fully selected and has a top module defined, topological sorting ensures that all cells
// added during flattening are black boxes, and flattening is finished in one pass. However, when flattening
// individual modules, this isn't the case, and the newly added cells might have to be flattened further.
- flatten_cell(design, module, cell, tpl, worklist);
+ flatten_cell(design, module, cell, tpl, sigmap, worklist);
}
}
};
diff --git a/passes/techmap/iopadmap.cc b/passes/techmap/iopadmap.cc
index 45fa5f226..437ad5156 100644
--- a/passes/techmap/iopadmap.cc
+++ b/passes/techmap/iopadmap.cc
@@ -43,26 +43,28 @@ struct IopadmapPass : public Pass {
log("can only map to very simple PAD cells. Use 'techmap' to further map\n");
log("the resulting cells to more sophisticated PAD cells.\n");
log("\n");
- log(" -inpad <celltype> <portname>[:<portname>]\n");
+ 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> <portname>[:<portname>]\n");
- log(" -inoutpad <celltype> <portname>[:<portname>]\n");
+ log(" -outpad <celltype> <out_port>[:<ext_port>]\n");
+ log(" -inoutpad <celltype> <io_port>[:<ext_port>]\n");
log(" Similar to -inpad, but for output and inout ports.\n");
log("\n");
- log(" -toutpad <celltype> <portname>:<portname>[:<portname>]\n");
+ log(" -toutpad <celltype> <oe_port>:<out_port>[:<ext_port>]\n");
log(" Merges $_TBUF_ cells into the output pad cell. This takes precedence\n");
log(" over the other -outpad cell. The first portname is the enable input\n");
- log(" of the tristate driver.\n");
+ log(" of the tristate driver, which can be prefixed with `~` for negative\n");
+ log(" polarity enable.\n");
log("\n");
- log(" -tinoutpad <celltype> <portname>:<portname>:<portname>[:<portname>]\n");
+ log(" -tinoutpad <celltype> <oe_port>:<in_port>:<out_port>[:<ext_port>]\n");
log(" Merges $_TBUF_ cells into the inout pad cell. This takes precedence\n");
log(" over the other -inoutpad cell. The first portname is the enable input\n");
log(" of the tristate driver and the 2nd portname is the internal output\n");
- log(" buffering the external signal.\n");
+ log(" buffering the external signal. Like with `-toutpad`, the enable can\n");
+ log(" be marked as negative polarity by prefixing the name with `~`.\n");
log("\n");
log(" -ignore <celltype> <portname>[:<portname>]*\n");
log(" Skips mapping inputs/outputs that are already connected to given\n");
@@ -106,6 +108,7 @@ struct IopadmapPass : public Pass {
std::string inoutpad_celltype, inoutpad_portname_io, inoutpad_portname_pad;
std::string toutpad_celltype, toutpad_portname_oe, toutpad_portname_i, toutpad_portname_pad;
std::string tinoutpad_celltype, tinoutpad_portname_oe, tinoutpad_portname_o, tinoutpad_portname_i, tinoutpad_portname_pad;
+ bool toutpad_neg_oe = false, tinoutpad_neg_oe = false;
std::string widthparam, nameparam;
pool<pair<IdString, IdString>> ignore;
bool flag_bits = false;
@@ -137,6 +140,10 @@ struct IopadmapPass : public Pass {
toutpad_portname_oe = args[++argidx];
split_portname_pair(toutpad_portname_oe, toutpad_portname_i);
split_portname_pair(toutpad_portname_i, toutpad_portname_pad);
+ if (toutpad_portname_oe[0] == '~') {
+ toutpad_neg_oe = true;
+ toutpad_portname_oe = toutpad_portname_oe.substr(1);
+ }
continue;
}
if (arg == "-tinoutpad" && argidx+2 < args.size()) {
@@ -145,6 +152,10 @@ struct IopadmapPass : public Pass {
split_portname_pair(tinoutpad_portname_oe, tinoutpad_portname_o);
split_portname_pair(tinoutpad_portname_o, tinoutpad_portname_i);
split_portname_pair(tinoutpad_portname_i, tinoutpad_portname_pad);
+ if (tinoutpad_portname_oe[0] == '~') {
+ tinoutpad_neg_oe = true;
+ tinoutpad_portname_oe = tinoutpad_portname_oe.substr(1);
+ }
continue;
}
if (arg == "-ignore" && argidx+2 < args.size()) {
@@ -318,6 +329,8 @@ struct IopadmapPass : public Pass {
module->uniquify(stringf("$iopadmap$%s.%s[%d]", log_id(module), log_id(wire), i)),
RTLIL::escape_id(tinoutpad_celltype));
+ if (tinoutpad_neg_oe)
+ en_sig = module->NotGate(NEW_ID, en_sig);
cell->setPort(RTLIL::escape_id(tinoutpad_portname_oe), en_sig);
cell->attributes[ID::keep] = RTLIL::Const(1);
@@ -340,6 +353,8 @@ struct IopadmapPass : public Pass {
module->uniquify(stringf("$iopadmap$%s.%s[%d]", log_id(module), log_id(wire), i)),
RTLIL::escape_id(toutpad_celltype));
+ if (toutpad_neg_oe)
+ en_sig = module->NotGate(NEW_ID, en_sig);
cell->setPort(RTLIL::escape_id(toutpad_portname_oe), en_sig);
cell->setPort(RTLIL::escape_id(toutpad_portname_i), data_sig);
cell->attributes[ID::keep] = RTLIL::Const(1);
diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc
index b65224c71..7d8dba439 100644
--- a/passes/techmap/simplemap.cc
+++ b/passes/techmap/simplemap.cc
@@ -19,6 +19,7 @@
#include "simplemap.h"
#include "kernel/sigtools.h"
+#include "kernel/ff.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -298,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);
@@ -305,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_));
@@ -367,276 +391,13 @@ void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell)
module->connect(RTLIL::SigSig(sig_y, sig_ab));
}
-void simplemap_sr(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N';
- char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_s = cell->getPort(ID::SET);
- RTLIL::SigSpec sig_r = cell->getPort(ID::CLR);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- std::string gate_type = stringf("$_SR_%c%c_", set_pol, clr_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::S, sig_s[i]);
- gate->setPort(ID::R, sig_r[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_ff(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
-
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = ID($_FF_);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dff(RTLIL::Module *module, RTLIL::Cell *cell)
+void simplemap_ff(RTLIL::Module *, RTLIL::Cell *cell)
{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DFF_%c_", clk_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dffe(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_en = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DFFE_%c%c_", clk_pol, en_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::E, sig_en);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dffsr(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
- char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N';
- char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_s = cell->getPort(ID::SET);
- RTLIL::SigSpec sig_r = cell->getPort(ID::CLR);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DFFSR_%c%c%c_", clk_pol, set_pol, clr_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::S, sig_s[i]);
- gate->setPort(ID::R, sig_r[i]);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dffsre(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
- char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N';
- char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N';
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_s = cell->getPort(ID::SET);
- RTLIL::SigSpec sig_r = cell->getPort(ID::CLR);
- RTLIL::SigSpec sig_e = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DFFSRE_%c%c%c%c_", clk_pol, set_pol, clr_pol, en_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::S, sig_s[i]);
- gate->setPort(ID::R, sig_r[i]);
- gate->setPort(ID::E, sig_e);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_adff_sdff(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- bool is_async = cell->type == ID($adff);
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
- char rst_pol = cell->parameters.at(is_async ? ID::ARST_POLARITY : ID::SRST_POLARITY).as_bool() ? 'P' : 'N';
- const char *type = is_async ? "DFF" : "SDFF";
-
- std::vector<RTLIL::State> rst_val = cell->parameters.at(is_async ? ID::ARST_VALUE : ID::SRST_VALUE).bits;
- while (int(rst_val.size()) < width)
- rst_val.push_back(RTLIL::State::S0);
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_rst = cell->getPort(is_async ? ID::ARST : ID::SRST);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type_0 = stringf("$_%s_%c%c0_", type, clk_pol, rst_pol);
- IdString gate_type_1 = stringf("$_%s_%c%c1_", type, clk_pol, rst_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::R, sig_rst);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_adffe_sdffe_sdffce(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- bool is_async = cell->type == ID($adffe);
- char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N';
- char rst_pol = cell->parameters.at(is_async ? ID::ARST_POLARITY : ID::SRST_POLARITY).as_bool() ? 'P' : 'N';
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
- const char *type = is_async ? "DFFE" : cell->type == ID($sdffe) ? "SDFFE" : "SDFFCE";
-
- std::vector<RTLIL::State> rst_val = cell->parameters.at(is_async ? ID::ARST_VALUE : ID::SRST_VALUE).bits;
- while (int(rst_val.size()) < width)
- rst_val.push_back(RTLIL::State::S0);
-
- RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK);
- RTLIL::SigSpec sig_rst = cell->getPort(is_async ? ID::ARST : ID::SRST);
- RTLIL::SigSpec sig_e = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type_0 = stringf("$_%s_%c%c0%c_", type, clk_pol, rst_pol, en_pol);
- IdString gate_type_1 = stringf("$_%s_%c%c1%c_", type, clk_pol, rst_pol, en_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::C, sig_clk);
- gate->setPort(ID::R, sig_rst);
- gate->setPort(ID::E, sig_e);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_en = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DLATCH_%c_", en_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::E, sig_en);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_adlatch(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
- char rst_pol = cell->parameters.at(ID::ARST_POLARITY).as_bool() ? 'P' : 'N';
-
- std::vector<RTLIL::State> rst_val = cell->parameters.at(ID::ARST_VALUE).bits;
- while (int(rst_val.size()) < width)
- rst_val.push_back(RTLIL::State::S0);
-
- RTLIL::SigSpec sig_en = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_rst = cell->getPort(ID::ARST);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type_0 = stringf("$_DLATCH_%c%c0_", en_pol, rst_pol);
- IdString gate_type_1 = stringf("$_DLATCH_%c%c1_", en_pol, rst_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::E, sig_en);
- gate->setPort(ID::R, sig_rst);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
- }
-}
-
-void simplemap_dlatchsr(RTLIL::Module *module, RTLIL::Cell *cell)
-{
- int width = cell->parameters.at(ID::WIDTH).as_int();
- char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N';
- char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N';
- char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N';
-
- RTLIL::SigSpec sig_en = cell->getPort(ID::EN);
- RTLIL::SigSpec sig_s = cell->getPort(ID::SET);
- RTLIL::SigSpec sig_r = cell->getPort(ID::CLR);
- RTLIL::SigSpec sig_d = cell->getPort(ID::D);
- RTLIL::SigSpec sig_q = cell->getPort(ID::Q);
-
- IdString gate_type = stringf("$_DLATCHSR_%c%c%c_", en_pol, set_pol, clr_pol);
-
- for (int i = 0; i < width; i++) {
- RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type);
- gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
- gate->setPort(ID::E, sig_en);
- gate->setPort(ID::S, sig_s[i]);
- gate->setPort(ID::R, sig_r[i]);
- gate->setPort(ID::D, sig_d[i]);
- gate->setPort(ID::Q, sig_q[i]);
+ FfData ff(nullptr, cell);
+ for (int i = 0; i < ff.width; i++) {
+ FfData fff = ff.slice({i});
+ fff.is_fine = true;
+ fff.emit();
}
}
@@ -662,24 +423,27 @@ 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;
mappers[ID($concat)] = simplemap_concat;
- mappers[ID($sr)] = simplemap_sr;
+ mappers[ID($sr)] = simplemap_ff;
mappers[ID($ff)] = simplemap_ff;
- mappers[ID($dff)] = simplemap_dff;
- mappers[ID($dffe)] = simplemap_dffe;
- mappers[ID($dffsr)] = simplemap_dffsr;
- mappers[ID($dffsre)] = simplemap_dffsre;
- mappers[ID($adff)] = simplemap_adff_sdff;
- mappers[ID($sdff)] = simplemap_adff_sdff;
- mappers[ID($adffe)] = simplemap_adffe_sdffe_sdffce;
- mappers[ID($sdffe)] = simplemap_adffe_sdffe_sdffce;
- mappers[ID($sdffce)] = simplemap_adffe_sdffe_sdffce;
- mappers[ID($dlatch)] = simplemap_dlatch;
- mappers[ID($adlatch)] = simplemap_adlatch;
- mappers[ID($dlatchsr)] = simplemap_dlatchsr;
+ mappers[ID($dff)] = simplemap_ff;
+ mappers[ID($dffe)] = simplemap_ff;
+ mappers[ID($dffsr)] = simplemap_ff;
+ mappers[ID($dffsre)] = simplemap_ff;
+ mappers[ID($adff)] = simplemap_ff;
+ mappers[ID($sdff)] = simplemap_ff;
+ mappers[ID($adffe)] = simplemap_ff;
+ mappers[ID($sdffe)] = simplemap_ff;
+ mappers[ID($sdffce)] = simplemap_ff;
+ mappers[ID($aldff)] = simplemap_ff;
+ mappers[ID($aldffe)] = simplemap_ff;
+ mappers[ID($dlatch)] = simplemap_ff;
+ mappers[ID($adlatch)] = simplemap_ff;
+ mappers[ID($dlatchsr)] = simplemap_ff;
}
void simplemap(RTLIL::Module *module, RTLIL::Cell *cell)
@@ -712,7 +476,7 @@ struct SimplemapPass : public Pass {
log(" $not, $pos, $and, $or, $xor, $xnor\n");
log(" $reduce_and, $reduce_or, $reduce_xor, $reduce_xnor, $reduce_bool\n");
log(" $logic_not, $logic_and, $logic_or, $mux, $tribuf\n");
- log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $sdff, $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n");
+ log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $aldff, $aldffe, $sdff, $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
diff --git a/passes/techmap/simplemap.h b/passes/techmap/simplemap.h
index 03a8fb36f..c7654f68c 100644
--- a/passes/techmap/simplemap.h
+++ b/passes/techmap/simplemap.h
@@ -34,12 +34,7 @@ extern void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell);
extern void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell);
extern void simplemap_slice(RTLIL::Module *module, RTLIL::Cell *cell);
extern void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_sr(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_dff(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_dffe(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_dffsr(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_adff(RTLIL::Module *module, RTLIL::Cell *cell);
-extern void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell);
+extern void simplemap_ff(RTLIL::Module *module, RTLIL::Cell *cell);
extern void simplemap(RTLIL::Module *module, RTLIL::Cell *cell);
extern void simplemap_get_mappers(dict<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers);
diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc
index a69a6d460..5cd78fe28 100644
--- a/passes/techmap/techmap.cc
+++ b/passes/techmap/techmap.cc
@@ -377,10 +377,12 @@ struct TechmapWorker
if (c->attributes.count(ID::src))
c->add_strpool_attribute(ID::src, extra_src_attrs);
- if (techmap_replace_cell)
+ if (techmap_replace_cell) {
for (auto attr : cell->attributes)
if (!c->attributes.count(attr.first))
c->attributes[attr.first] = attr.second;
+ c->attributes.erase(ID::reprocess_after);
+ }
}
for (auto &it : tpl->connections()) {
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/techmap/zinit.cc b/passes/techmap/zinit.cc
index 7c5b73c90..cc208c516 100644
--- a/passes/techmap/zinit.cc
+++ b/passes/techmap/zinit.cc
@@ -20,6 +20,7 @@
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include "kernel/ffinit.h"
+#include "kernel/ff.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@@ -60,123 +61,25 @@ struct ZinitPass : public Pass {
SigMap sigmap(module);
FfInitVals initvals(&sigmap, module);
- pool<IdString> dff_types = {
- // FIXME: It would appear that supporting
- // $dffsr/$_DFFSR_* would require a new
- // cell type where S has priority over R
- ID($ff), ID($dff), ID($dffe), /*ID($dffsr),*/ ID($adff), ID($adffe),
- ID($sdff), ID($sdffe), ID($sdffce),
- ID($_FF_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_),
- /*ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_),
- ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_),*/
- ID($_DFF_N_), ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
- ID($_DFF_P_), ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_),
- // Async set/reset
- ID($_DFFE_NN0P_), ID($_DFFE_NN1P_), ID($_DFFE_NP0P_), ID($_DFFE_NP1P_),
- ID($_DFFE_PN0P_), ID($_DFFE_PN1P_), ID($_DFFE_PP0P_), ID($_DFFE_PP1P_),
- ID($_DFFE_NN0N_), ID($_DFFE_NN1N_), ID($_DFFE_NP0N_), ID($_DFFE_NP1N_),
- ID($_DFFE_PN0N_), ID($_DFFE_PN1N_), ID($_DFFE_PP0N_), ID($_DFFE_PP1N_),
- // Sync set/reset
- ID($_SDFF_NN0_), ID($_SDFF_NN1_), ID($_SDFF_NP0_), ID($_SDFF_NP1_),
- ID($_SDFF_PN0_), ID($_SDFF_PN1_), ID($_SDFF_PP0_), ID($_SDFF_PP1_),
- ID($_SDFFE_NN0P_), ID($_SDFFE_NN1P_), ID($_SDFFE_NP0P_), ID($_SDFFE_NP1P_),
- ID($_SDFFE_PN0P_), ID($_SDFFE_PN1P_), ID($_SDFFE_PP0P_), ID($_SDFFE_PP1P_),
- ID($_SDFFE_NN0N_), ID($_SDFFE_NN1N_), ID($_SDFFE_NP0N_), ID($_SDFFE_NP1N_),
- ID($_SDFFE_PN0N_), ID($_SDFFE_PN1N_), ID($_SDFFE_PP0N_), ID($_SDFFE_PP1N_),
- ID($_SDFFCE_NN0P_), ID($_SDFFCE_NN1P_), ID($_SDFFCE_NP0P_), ID($_SDFFCE_NP1P_),
- ID($_SDFFCE_PN0P_), ID($_SDFFCE_PN1P_), ID($_SDFFCE_PP0P_), ID($_SDFFCE_PP1P_),
- ID($_SDFFCE_NN0N_), ID($_SDFFCE_NN1N_), ID($_SDFFCE_NP0N_), ID($_SDFFCE_NP1N_),
- ID($_SDFFCE_PN0N_), ID($_SDFFCE_PN1N_), ID($_SDFFCE_PP0N_), ID($_SDFFCE_PP1N_)
- };
-
for (auto cell : module->selected_cells())
{
- if (!dff_types.count(cell->type))
- continue;
-
- SigSpec sig_d = sigmap(cell->getPort(ID::D));
- SigSpec sig_q = sigmap(cell->getPort(ID::Q));
-
- if (GetSize(sig_d) < 1 || GetSize(sig_q) < 1)
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type))
continue;
- Const initval = initvals(sig_q);
- Const newval = initval;
- initvals.remove_init(sig_q);
-
- Wire *initwire = module->addWire(NEW_ID, GetSize(sig_q));
-
- for (int i = 0; i < GetSize(initwire); i++)
- if (initval[i] == State::S1)
- {
- sig_d[i] = module->NotGate(NEW_ID, sig_d[i]);
- module->addNotGate(NEW_ID, SigSpec(initwire, i), sig_q[i]);
- newval[i] = State::S0;
- }
- else
- {
- module->connect(sig_q[i], SigSpec(initwire, i));
- if (all_mode)
- newval[i] = State::S0;
- }
-
- initvals.set_init(initwire, newval);
+ FfData ff(&initvals, cell);
log("FF init value for cell %s (%s): %s = %s\n", log_id(cell), log_id(cell->type),
- log_signal(sig_q), log_signal(initval));
-
- cell->setPort(ID::D, sig_d);
- cell->setPort(ID::Q, initwire);
-
- if (cell->type.in(ID($adff), ID($adffe))) {
- auto val = cell->getParam(ID::ARST_VALUE);
- for (int i = 0; i < GetSize(initwire); i++)
- if (initval[i] == State::S1)
- val[i] = (val[i] == State::S1 ? State::S0 : State::S1);
- cell->setParam(ID::ARST_VALUE, std::move(val));
- }
- else if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) {
- auto val = cell->getParam(ID::SRST_VALUE);
- for (int i = 0; i < GetSize(initwire); i++)
- if (initval[i] == State::S1)
- val[i] = (val[i] == State::S1 ? State::S0 : State::S1);
- cell->setParam(ID::SRST_VALUE, std::move(val));
- }
- else if (initval == State::S1) {
- std::string t = cell->type.str();
- if (cell->type.in(ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
- ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_)))
- {
- t[8] = (t[8] == '0' ? '1' : '0');
- }
- else if (cell->type.in(ID($_SDFF_NN0_), ID($_SDFF_NN1_), ID($_SDFF_NP0_), ID($_SDFF_NP1_),
- ID($_SDFF_PN0_), ID($_SDFF_PN1_), ID($_SDFF_PP0_), ID($_SDFF_PP1_)))
- {
- t[9] = (t[9] == '0' ? '1' : '0');
- }
- else if (cell->type.in(ID($_DFFE_NN0P_), ID($_DFFE_NN1P_), ID($_DFFE_NP0P_), ID($_DFFE_NP1P_),
- ID($_DFFE_PN0P_), ID($_DFFE_PN1P_), ID($_DFFE_PP0P_), ID($_DFFE_PP1P_),
- ID($_DFFE_NN0N_), ID($_DFFE_NN1N_), ID($_DFFE_NP0N_), ID($_DFFE_NP1N_),
- ID($_DFFE_PN0N_), ID($_DFFE_PN1N_), ID($_DFFE_PP0N_), ID($_DFFE_PP1N_)))
- {
- t[9] = (t[9] == '0' ? '1' : '0');
- }
- else if (cell->type.in(ID($_SDFFE_NN0P_), ID($_SDFFE_NN1P_), ID($_SDFFE_NP0P_), ID($_SDFFE_NP1P_),
- ID($_SDFFE_PN0P_), ID($_SDFFE_PN1P_), ID($_SDFFE_PP0P_), ID($_SDFFE_PP1P_),
- ID($_SDFFE_NN0N_), ID($_SDFFE_NN1N_), ID($_SDFFE_NP0N_), ID($_SDFFE_NP1N_),
- ID($_SDFFE_PN0N_), ID($_SDFFE_PN1N_), ID($_SDFFE_PP0N_), ID($_SDFFE_PP1N_)))
- {
- t[10] = (t[10] == '0' ? '1' : '0');
- }
- else if (cell->type.in(ID($_SDFFCE_NN0P_), ID($_SDFFCE_NN1P_), ID($_SDFFCE_NP0P_), ID($_SDFFCE_NP1P_),
- ID($_SDFFCE_PN0P_), ID($_SDFFCE_PN1P_), ID($_SDFFCE_PP0P_), ID($_SDFFCE_PP1P_),
- ID($_SDFFCE_NN0N_), ID($_SDFFCE_NN1N_), ID($_SDFFCE_NP0N_), ID($_SDFFCE_NP1N_),
- ID($_SDFFCE_PN0N_), ID($_SDFFCE_PN1N_), ID($_SDFFCE_PP0N_), ID($_SDFFCE_PP1N_)))
- {
- t[11] = (t[11] == '0' ? '1' : '0');
- }
- cell->type = t;
+ log_signal(ff.sig_q), log_signal(ff.val_init));
+
+ pool<int> bits;
+ for (int i = 0; i < ff.width; i++) {
+ if (ff.val_init.bits[i] == State::S1)
+ bits.insert(i);
+ else if (ff.val_init.bits[i] != State::S0 && all_mode)
+ ff.val_init.bits[i] = State::S0;
}
+ ff.flip_bits(bits);
+ ff.emit();
}
}
}
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)] = "*";
}