diff options
Diffstat (limited to 'passes')
-rw-r--r-- | passes/cmds/Makefile.inc | 1 | ||||
-rw-r--r-- | passes/cmds/clean_zerowidth.cc | 2 | ||||
-rw-r--r-- | passes/cmds/glift.cc | 599 | ||||
-rw-r--r-- | passes/cmds/stat.cc | 4 | ||||
-rw-r--r-- | passes/memory/memory_bram.cc | 28 | ||||
-rw-r--r-- | passes/opt/opt_dff.cc | 210 | ||||
-rw-r--r-- | passes/opt/opt_reduce.cc | 397 | ||||
-rw-r--r-- | passes/pmgen/ice40_dsp.pmg | 5 | ||||
-rw-r--r-- | passes/sat/clk2fflogic.cc | 29 | ||||
-rw-r--r-- | passes/sat/sim.cc | 1465 | ||||
-rw-r--r-- | passes/techmap/Makefile.inc | 2 | ||||
-rw-r--r-- | passes/techmap/abc.cc | 4 | ||||
-rw-r--r-- | passes/techmap/abc9_ops.cc | 15 | ||||
-rw-r--r-- | passes/techmap/bmuxmap.cc | 76 | ||||
-rw-r--r-- | passes/techmap/demuxmap.cc | 80 | ||||
-rw-r--r-- | passes/techmap/iopadmap.cc | 2 | ||||
-rw-r--r-- | passes/techmap/simplemap.cc | 26 | ||||
-rw-r--r-- | passes/tests/test_cell.cc | 46 |
18 files changed, 2705 insertions, 286 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 917856767..16a38b511 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -18,6 +18,7 @@ OBJS += passes/cmds/setattr.o OBJS += passes/cmds/copy.o OBJS += passes/cmds/splice.o OBJS += passes/cmds/scc.o +OBJS += passes/cmds/glift.o OBJS += passes/cmds/torder.o OBJS += passes/cmds/logcmd.o OBJS += passes/cmds/tee.o diff --git a/passes/cmds/clean_zerowidth.cc b/passes/cmds/clean_zerowidth.cc index 4e7c68093..bac6b1521 100644 --- a/passes/cmds/clean_zerowidth.cc +++ b/passes/cmds/clean_zerowidth.cc @@ -80,7 +80,7 @@ struct CleanZeroWidthPass : public Pass { if (GetSize(cell->getPort(ID::Q)) == 0) { module->remove(cell); } - } else if (cell->type == ID($pmux)) { + } else if (cell->type.in(ID($pmux), ID($bmux), ID($demux))) { // Remove altogether if WIDTH is 0, replace with // a connection if S_WIDTH is 0. if (cell->getParam(ID::WIDTH).as_int() == 0) { diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc new file mode 100644 index 000000000..b398c3e04 --- /dev/null +++ b/passes/cmds/glift.cc @@ -0,0 +1,599 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 Alberto Gonzalez <boqwxp@airmail.cc> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/utils.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct GliftWorker { +private: + bool is_top_module = false; + bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false; + bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false; + bool opt_instrumentmore = false; + std::vector<RTLIL::Wire *> new_taint_outputs; + std::vector<std::pair<RTLIL::SigSpec, RTLIL::IdString>> meta_mux_selects; + RTLIL::Module *module = nullptr; + + const RTLIL::IdString cost_model_wire_name = ID(__glift_weight); + const RTLIL::IdString glift_attribute_name = ID(glift); + + + RTLIL::SigSpec get_corresponding_taint_signal(RTLIL::SigSpec sig) { + RTLIL::SigSpec ret; + + //Get the connected wire for the cell port: + log_assert(sig.is_wire() || sig.is_fully_const()); + log_assert(sig.is_wire() || sig.is_fully_const()); + + //Get a SigSpec for the corresponding taint signal for the cell port, creating one if necessary: + if (sig.is_wire()) { + RTLIL::Wire *w = module->wire(sig.as_wire()->name.str() + "_t"); + if (w == nullptr) w = module->addWire(sig.as_wire()->name.str() + "_t", 1); + ret = w; + } + else if (sig.is_fully_const() && opt_taintconstants) + ret = RTLIL::State::S1; + else if (sig.is_fully_const()) + ret = RTLIL::State::S0; + else + log_cmd_error("Cell port SigSpec has unexpected type.\n"); + + //Finally, if the cell port was a module input or output, make sure the corresponding taint signal is marked, too: + if(sig.is_wire() && sig.as_wire()->port_input) + ret.as_wire()->port_input = true; + if(sig.is_wire() && sig.as_wire()->port_output) + new_taint_outputs.push_back(ret.as_wire()); + + return ret; + } + + void add_precise_GLIFT_logic(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH2 or OR2_SH2 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_1_1", port_a, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_1_2", port_b, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_1_3", is_and? port_a : n_port_a, port_b_taint, false, cell->get_src_attribute()); + auto subexpr2 = module->And(cell->name.str() + "_t_1_4", is_and? port_b : n_port_b, port_a_taint, false, cell->get_src_attribute()); + auto subexpr3 = module->And(cell->name.str() + "_t_1_5", port_a_taint, port_b_taint, false, cell->get_src_attribute()); + auto subexpr4 = module->Or(cell->name.str() + "_t_1_6", subexpr1, subexpr2, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_1_7", subexpr4, subexpr3, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_1(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH3 or OR2_SH3 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_2_1", port_a, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_2_2", is_and? port_b : n_port_a, is_and? port_a_taint : port_b_taint, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_2_3", is_and? port_b_taint : port_a_taint, subexpr1, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_2(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH4 or OR2_SH4 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_3_1", port_b, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_3_2", is_and? port_a : n_port_b, is_and? port_b_taint : port_a_taint, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_3_3", is_and? port_a_taint : port_b_taint, subexpr1, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_3(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH5 or OR2_SH5 or XR2_SH2 + module->addOr(cell->name.str() + "_t_4_1", port_a_taint, port_b_taint, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_4(RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, port_a_taint); + } + + void add_imprecise_GLIFT_logic_5(RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, port_b_taint); + } + + void add_imprecise_GLIFT_logic_6(RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, RTLIL::Const(1, 1)); + } + + void add_imprecise_GLIFT_logic_7(RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, RTLIL::Const(0, 1)); + } + + void add_precise_GLIFT_mux(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_s, RTLIL::SigSpec &port_s_taint, RTLIL::SigSpec &port_y_taint) { + //S&At | ~S&Bt | ~A&B&St | A&~B&St | At&St | Bt&St + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_4_1", port_a, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_4_2", port_b, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_s = module->LogicNot(cell->name.str() + "_t_4_3", port_s, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_4_4", port_s, port_a_taint, false, cell->get_src_attribute()); + auto subexpr2 = module->And(cell->name.str() + "_t_4_5", n_port_s, port_b_taint, false, cell->get_src_attribute()); + auto subexpr3 = module->And(cell->name.str() + "_t_4_6", n_port_a, port_b, false, cell->get_src_attribute()); + auto subexpr4 = module->And(cell->name.str() + "_t_4_7", subexpr3, port_s_taint, false, cell->get_src_attribute()); + auto subexpr5 = module->And(cell->name.str() + "_t_4_8", port_a, n_port_b, false, cell->get_src_attribute()); + auto subexpr6 = module->And(cell->name.str() + "_t_4_9", subexpr5, port_s_taint, false, cell->get_src_attribute()); + auto subexpr7 = module->And(cell->name.str() + "_t_4_10", port_a_taint, port_s_taint, false, cell->get_src_attribute()); + auto subexpr8 = module->And(cell->name.str() + "_t_4_11", port_b_taint, port_s_taint, false, cell->get_src_attribute()); + auto subexpr9 = module->Or(cell->name.str() + "_t_4_12", subexpr1, subexpr2, false, cell->get_src_attribute()); + auto subexpr10 = module->Or(cell->name.str() + "_t_4_13", subexpr4, subexpr6, false, cell->get_src_attribute()); + auto subexpr11 = module->Or(cell->name.str() + "_t_4_14", subexpr7, subexpr8, false, cell->get_src_attribute()); + auto subexpr12 = module->Or(cell->name.str() + "_t_4_15", subexpr9, subexpr10, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_4_16", subexpr11, subexpr12, port_y_taint, false, cell->get_src_attribute()); + } + + RTLIL::SigSpec score_metamux_select(const RTLIL::SigSpec &metamux_select, const RTLIL::IdString celltype) { + log_assert(metamux_select.is_wire()); + + if (opt_simplecostmodel) { + //The complex model is an area model, so a lower score should mean smaller. + //In this case, a nonzero hole metamux select value means less logic. + //Thus we should invert the ReduceOr over the metamux_select signal. + RTLIL::SigSpec pmux_select = module->ReduceOr(metamux_select.as_wire()->name.str() + "_nonzero", metamux_select); + return module->Pmux(NEW_ID, RTLIL::Const(1), RTLIL::Const(0), pmux_select, metamux_select.as_wire()->get_src_attribute()); + } else { + auto select_width = metamux_select.as_wire()->width; + + std::vector<RTLIL::Const> costs; + if (celltype == ID($_AND_) || celltype == ID($_OR_)) { + costs = {5, 2, 2, 1, 0, 0, 0, 0}; + log_assert(select_width == 2 || select_width == 3); + log_assert(opt_instrumentmore || select_width == 2); + log_assert(!opt_instrumentmore || select_width == 3); + } + else if (celltype == ID($_XOR_) || celltype == ID($_XNOR_)) { + costs = {1, 0, 0, 0}; + log_assert(select_width == 2); + } + + std::vector<RTLIL::SigSpec> next_pmux_y_ports, pmux_y_ports(costs.begin(), costs.begin() + exp2(select_width)); + for (auto i = 0; pmux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(pmux_y_ports); j += 2) { + next_pmux_y_ports.emplace_back(module->Pmux(stringf("%s_mux_%d_%d", metamux_select.as_wire()->name.c_str(), i, j), pmux_y_ports[j], pmux_y_ports[j+1], metamux_select[GetSize(metamux_select) - 1 - i], metamux_select.as_wire()->get_src_attribute())); + } + if (GetSize(pmux_y_ports) % 2 == 1) + next_pmux_y_ports.push_back(pmux_y_ports[GetSize(pmux_y_ports) - 1]); + pmux_y_ports.swap(next_pmux_y_ports); + next_pmux_y_ports.clear(); + } + + log_assert(pmux_y_ports.size() == 1); + return pmux_y_ports[0]; + } + } + + void create_glift_logic() { + if (module->get_bool_attribute(glift_attribute_name)) + return; + + std::vector<RTLIL::SigSig> connections(module->connections()); + + for(auto &cell : module->cells().to_vector()) { + if (!cell->type.in({ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_MUX_), ID($_NMUX_), ID($_NOT_), ID($anyconst), ID($allconst), ID($assume), ID($assert)}) && module->design->module(cell->type) == nullptr) { + log_cmd_error("Unsupported cell type \"%s\" found. Run `techmap` first.\n", cell->type.c_str()); + } + if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_))) { + const unsigned int A = 0, B = 1, Y = 2; + const unsigned int NUM_PORTS = 3; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (opt_create_precise_model) + add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], port_taints[Y]); + else if (opt_create_imprecise_model) + add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]); + else if (opt_create_instrumented_model) { + std::vector<RTLIL::SigSpec> taint_version; + int num_versions = opt_instrumentmore? 8 : 4; + + for (auto i = 1; i <= num_versions; ++i) + taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1))); + + for (auto i = 0; i < num_versions; ++i) { + switch(i) { + case 0: add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 1: add_imprecise_GLIFT_logic_1(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 2: add_imprecise_GLIFT_logic_2(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 3: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]); + break; + case 4: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]); + break; + case 5: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]); + break; + case 6: add_imprecise_GLIFT_logic_6(taint_version[i]); + break; + case 7: add_imprecise_GLIFT_logic_7(taint_version[i]); + break; + default: log_assert(false); + } + } + + auto select_width = log2(num_versions); + log_assert(exp2(select_width) == num_versions); + RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width)); + meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type)); + module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute())); + + std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version); + for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) { + next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i])); + } + if (GetSize(meta_mux_y_ports) % 2 == 1) + next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]); + meta_mux_y_ports.swap(next_meta_mux_y_ports); + next_meta_mux_y_ports.clear(); + } + log_assert(meta_mux_y_ports.size() == 1); + module->connect(port_taints[Y], meta_mux_y_ports[0]); + } + else log_cmd_error("This is a bug (1).\n"); + } + else if (cell->type.in(ID($_XOR_), ID($_XNOR_))) { + const unsigned int A = 0, B = 1, Y = 2; + const unsigned int NUM_PORTS = 3; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (opt_create_precise_model || opt_create_imprecise_model) + add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]); + else if (opt_create_instrumented_model) { + std::vector<RTLIL::SigSpec> taint_version; + int num_versions = 4; + auto select_width = log2(num_versions); + log_assert(exp2(select_width) == num_versions); + + for (auto i = 1; i <= num_versions; ++i) + taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1))); + + for (auto i = 0; i < num_versions; ++i) { + switch(i) { + case 0: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]); + break; + case 1: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]); + break; + case 2: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]); + break; + case 3: add_imprecise_GLIFT_logic_6(taint_version[i]); + break; + default: log_assert(false); + } + } + + RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width)); + meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type)); + module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute())); + + std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version); + for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) { + next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i])); + } + if (GetSize(meta_mux_y_ports) % 2 == 1) + next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]); + meta_mux_y_ports.swap(next_meta_mux_y_ports); + next_meta_mux_y_ports.clear(); + } + log_assert(meta_mux_y_ports.size() == 1); + module->connect(port_taints[Y], meta_mux_y_ports[0]); + } + else log_cmd_error("This is a bug (2).\n"); + + } + else if (cell->type.in(ID($_MUX_), ID($_NMUX_))) { + const unsigned int A = 0, B = 1, S = 2, Y = 3; + const unsigned int NUM_PORTS = 4; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::S), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[S].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + add_precise_GLIFT_mux(cell, ports[A], port_taints[A], ports[B], port_taints[B], ports[S], port_taints[S], port_taints[Y]); + } + else if (cell->type.in(ID($_NOT_))) { + const unsigned int A = 0, Y = 1; + const unsigned int NUM_PORTS = 2; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (cell->type == ID($_NOT_)) { + module->connect(port_taints[Y], port_taints[A]); + } + else log_cmd_error("This is a bug (3).\n"); + } + else if (module->design->module(cell->type) != nullptr) { + //User cell type + //This function is called on modules according to topological order, so we do not need to + //recurse to GLIFT model the child module. However, we need to augment the ports list + //with taint signals and connect the new ports to the corresponding taint signals. + RTLIL::Module *cell_module_def = module->design->module(cell->type); + dict<RTLIL::IdString, RTLIL::SigSpec> orig_ports = cell->connections(); + log("Adding cell %s\n", cell_module_def->name.c_str()); + for (auto &it : orig_ports) { + RTLIL::SigSpec port = it.second; + RTLIL::SigSpec port_taint = get_corresponding_taint_signal(port); + + log_assert(port_taint.is_wire()); + log_assert(std::find(cell_module_def->ports.begin(), cell_module_def->ports.end(), port_taint.as_wire()->name) != cell_module_def->ports.end()); + cell->setPort(port_taint.as_wire()->name, port_taint); + } + } + else log_cmd_error("This is a bug (4).\n"); + } //end foreach cell in cells + + for (auto &conn : connections) { + RTLIL::SigSpec first = get_corresponding_taint_signal(conn.first); + RTLIL::SigSpec second = get_corresponding_taint_signal(conn.second); + + module->connect(first, second); + + if(conn.second.is_wire() && conn.second.as_wire()->port_input) + second.as_wire()->port_input = true; + if(conn.first.is_wire() && conn.first.as_wire()->port_output) + new_taint_outputs.push_back(first.as_wire()); + } //end foreach conn in connections + + //Create a rough model of area by summing the (potentially simplified) "weight" score of each meta-mux select: + if (!opt_nocostmodel) { + std::vector<RTLIL::SigSpec> meta_mux_select_sums; + std::vector<RTLIL::SigSpec> meta_mux_select_sums_buf; + for (auto &it : meta_mux_selects) { + meta_mux_select_sums.emplace_back(score_metamux_select(it.first, it.second)); + } + for (unsigned int i = 0; meta_mux_select_sums.size() > 1; ) { + meta_mux_select_sums_buf.clear(); + for (i = 0; i + 1 < meta_mux_select_sums.size(); i += 2) { + meta_mux_select_sums_buf.push_back(module->Add(meta_mux_select_sums[i].as_wire()->name.str() + "_add", meta_mux_select_sums[i], meta_mux_select_sums[i+1], false)); + } + if (meta_mux_select_sums.size() % 2 == 1) + meta_mux_select_sums_buf.push_back(meta_mux_select_sums[meta_mux_select_sums.size()-1]); + meta_mux_select_sums.swap(meta_mux_select_sums_buf); + } + if (meta_mux_select_sums.size() > 0) { + meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\minimize"); + meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\keep"); + module->rename(meta_mux_select_sums[0].as_wire(), cost_model_wire_name); + } + } + + //Mark new module outputs: + for (auto &port_name : module->ports) { + RTLIL::Wire *port = module->wire(port_name); + log_assert(port != nullptr); + if (is_top_module && port->port_output && !opt_keepoutputs) + port->port_output = false; + } + for (auto &output : new_taint_outputs) + output->port_output = true; + module->fixup_ports(); //we have some new taint signals in the module interface + module->set_bool_attribute(glift_attribute_name, true); + } + +public: + GliftWorker(RTLIL::Module *_module, bool _is_top_module, bool _opt_create_precise_model, bool _opt_create_imprecise_model, bool _opt_create_instrumented_model, bool _opt_taintconstants, bool _opt_keepoutputs, bool _opt_simplecostmodel, bool _opt_nocostmodel, bool _opt_instrumentmore) { + module = _module; + is_top_module = _is_top_module; + opt_create_precise_model = _opt_create_precise_model; + opt_create_imprecise_model = _opt_create_imprecise_model; + opt_create_instrumented_model = _opt_create_instrumented_model; + opt_taintconstants = _opt_taintconstants; + opt_keepoutputs = _opt_keepoutputs; + opt_simplecostmodel = _opt_simplecostmodel; + opt_nocostmodel = _opt_nocostmodel; + opt_instrumentmore = _opt_instrumentmore; + + create_glift_logic(); + } +}; + +struct GliftPass : public Pass { + GliftPass() : Pass("glift", "create GLIFT models and optimization problems") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" glift <command> [options] [selection]\n"); + log("\n"); + log("Augments the current or specified module with gate-level information flow tracking\n"); + log("(GLIFT) logic using the \"constructive mapping\" approach. Also can set up QBF-SAT\n"); + log("optimization problems in order to optimize GLIFT models or trade off precision and\n"); + log("complexity.\n"); + log("\n"); + log("\n"); + log("Commands:\n"); + log("\n"); + log(" -create-precise-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with precise taint tracking logic.\n"); + log(" For example, precise taint tracking logic for an AND gate is:\n"); + log("\n"); + log(" y_t = a & b_t | b & a_t | a_t & b_t\n"); + log("\n"); + log("\n"); + log(" -create-imprecise-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with imprecise \"All OR\" taint tracking\n"); + log(" logic:\n"); + log("\n"); + log(" y_t = a_t | b_t\n"); + log("\n"); + log("\n"); + log(" -create-instrumented-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with 4 varying-precision versions of taint\n"); + log(" tracking logic. Which version of taint tracking logic is used for a given gate is\n"); + log(" determined by a MUX selected by an $anyconst cell. By default, unless the\n"); + log(" `-no-cost-model` option is provided, an additional wire named `__glift_weight` with\n"); + log(" the `keep` and `minimize` attributes is added to the module along with pmuxes and\n"); + log(" adders to calculate a rough estimate of the number of logic gates in the GLIFT model\n"); + log(" given an assignment for the $anyconst cells. The four versions of taint tracking logic\n"); + log(" for an AND gate are:"); + log("\n"); + log(" y_t = a & b_t | b & a_t | a_t & b_t (like `-create-precise-model`)\n"); + log(" y_t = a_t | a & b_t\n"); + log(" y_t = b_t | b & a_t\n"); + log(" y_t = a_t | b_t (like `-create-imprecise-model`)\n"); + log("\n"); + log("\n"); + log("Options:\n"); + log("\n"); + log(" -taint-constants\n"); + log(" Constant values in the design are labeled as tainted.\n"); + log(" (default: label constants as un-tainted)\n"); + log("\n"); + log(" -keep-outputs\n"); + log(" Do not remove module outputs. Taint tracking outputs will appear in the module ports\n"); + log(" alongside the orignal outputs.\n"); + log(" (default: original module outputs are removed)\n"); + log("\n"); + log(" -simple-cost-model\n"); + log(" Do not model logic area. Instead model the number of non-zero assignments to $anyconsts.\n"); + log(" Taint tracking logic versions vary in their size, but all reduced-precision versions are\n"); + log(" significantly smaller than the fully-precise version. A non-zero $anyconst assignment means\n"); + log(" that reduced-precision taint tracking logic was chosen for some gate.\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: use a complex model and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log("\n"); + log(" -no-cost-model\n"); + log(" Do not model taint tracking logic area and do not create a `__glift_weight` wire.\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: model area and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log("\n"); + log(" -instrument-more\n"); + log(" Allow choice from more versions of (even simpler) taint tracking logic. A total\n"); + log(" of 8 versions of taint tracking logic will be added per gate, including the 4\n"); + log(" versions from `-create-instrumented-model` and these additional versions:\n"); + log("\n"); + log(" y_t = a_t\n"); + log(" y_t = b_t\n"); + log(" y_t = 1\n"); + log(" y_t = 0\n"); + log("\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: do not add more versions of taint tracking logic.\n"); + log("\n"); + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false; + bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false; + bool opt_instrumentmore = false; + log_header(design, "Executing GLIFT pass (creating and manipulating GLIFT models).\n"); + std::vector<std::string>::size_type argidx; + + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-create-precise-model") { + opt_create_precise_model = true; + continue; + } + if (args[argidx] == "-create-imprecise-model") { + opt_create_imprecise_model = true; + continue; + } + if (args[argidx] == "-create-instrumented-model") { + opt_create_instrumented_model = true; + continue; + } + if (args[argidx] == "-taint-constants") { + opt_taintconstants = true; + continue; + } + if (args[argidx] == "-keep-outputs") { + opt_keepoutputs = true; + continue; + } + if (args[argidx] == "-simple-cost-model") { + opt_simplecostmodel = true; + continue; + } + if (args[argidx] == "-no-cost-model") { + opt_nocostmodel = true; + continue; + } + if (args[argidx] == "-instrument-more") { + opt_instrumentmore = true; + continue; + } + break; + } + if(!opt_create_precise_model && !opt_create_imprecise_model && !opt_create_instrumented_model) + log_cmd_error("No command provided. See help for usage.\n"); + if(static_cast<int>(opt_create_precise_model) + static_cast<int>(opt_create_imprecise_model) + static_cast<int>(opt_create_instrumented_model) != 1) + log_cmd_error("Only one command may be specified. See help for usage.\n"); + if(opt_simplecostmodel && opt_nocostmodel) + log_cmd_error("Only one of `-simple-cost-model` and `-no-cost-model` may be specified. See help for usage.\n"); + if((opt_simplecostmodel || opt_nocostmodel) && !opt_create_instrumented_model) + log_cmd_error("Options `-simple-cost-model` and `-no-cost-model` may only be used with `-create-instrumented-model`. See help for usage.\n"); + extra_args(args, argidx, design); + + if (GetSize(design->selected_modules()) == 0) + log_cmd_error("Can't operate on an empty selection!\n"); + + TopoSort<RTLIL::Module*, IdString::compare_ptr_by_name<RTLIL::Module>> topo_modules; //cribbed from passes/techmap/flatten.cc + auto worklist = design->selected_modules(); + pool<RTLIL::IdString> non_top_modules; + while (!worklist.empty()) { + RTLIL::Module *module = *(worklist.begin()); + worklist.erase(worklist.begin()); + topo_modules.node(module); + + for (auto cell : module->selected_cells()) { + RTLIL::Module *tpl = design->module(cell->type); + if (tpl != nullptr) { + if (topo_modules.database.count(tpl) == 0) + worklist.push_back(tpl); + topo_modules.edge(tpl, module); + non_top_modules.insert(cell->type); + } + } + } + + if (!topo_modules.sort()) + log_cmd_error("Cannot handle recursive module instantiations.\n"); + + for (auto i = 0; i < GetSize(topo_modules.sorted); ++i) { + RTLIL::Module *module = topo_modules.sorted[i]; + GliftWorker(module, !non_top_modules[module->name], opt_create_precise_model, opt_create_imprecise_model, opt_create_instrumented_model, opt_taintconstants, opt_keepoutputs, opt_simplecostmodel, opt_nocostmodel, opt_instrumentmore); + } + } +} GliftPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 14a27ed99..fffdda48e 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -117,6 +117,10 @@ struct statdata_t } else if (cell_type.in(ID($mux), ID($pmux))) cell_type = stringf("%s_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y))); + else if (cell_type == ID($bmux)) + cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y)), GetSize(cell->getPort(ID::S))); + else if (cell_type == ID($demux)) + cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::S))); else if (cell_type.in( ID($sr), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($sdff), ID($sdffe), ID($sdffce), 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/opt/opt_dff.cc b/passes/opt/opt_dff.cc index 98b66dee3..73d674c8d 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) @@ -569,100 +566,6 @@ struct OptDffWorker changed = true; } - // Now check if any bit can be replaced by a constant. - pool<int> removed_sigbits; - for (int i = 0; i < ff.width; i++) { - State val = ff.val_init[i]; - if (ff.has_arst) - val = combine_const(val, ff.val_arst[i]); - if (ff.has_srst) - val = combine_const(val, ff.val_srst[i]); - if (ff.has_sr) { - if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1)) - val = combine_const(val, State::S0); - if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1)) - val = combine_const(val, State::S1); - } - if (val == State::Sm) - continue; - if (ff.has_clk || ff.has_gclk) { - if (!ff.sig_d[i].wire) { - val = combine_const(val, ff.sig_d[i].data); - if (val == State::Sm) - continue; - } else { - if (!opt.sat) - continue; - // For each register bit, try to prove that it cannot change from the initial value. If so, remove it - if (!modwalker.has_drivers(ff.sig_d.extract(i))) - continue; - if (val != State::S0 && val != State::S1) - continue; - - int init_sat_pi = qcsat.importSigBit(val); - int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); - int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]); - - qcsat.prepare(); - - // Try to find out whether the register bit can change under some circumstances - bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); - - // If the register bit cannot change, we can replace it with a constant - if (counter_example_found) - continue; - } - } - if (ff.has_aload) { - if (!ff.sig_ad[i].wire) { - val = combine_const(val, ff.sig_ad[i].data); - if (val == State::Sm) - continue; - } else { - if (!opt.sat) - continue; - // For each register bit, try to prove that it cannot change from the initial value. If so, remove it - if (!modwalker.has_drivers(ff.sig_ad.extract(i))) - continue; - if (val != State::S0 && val != State::S1) - continue; - - int init_sat_pi = qcsat.importSigBit(val); - int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); - int d_sat_pi = qcsat.importSigBit(ff.sig_ad[i]); - - qcsat.prepare(); - - // Try to find out whether the register bit can change under some circumstances - bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); - - // If the register bit cannot change, we can replace it with a constant - if (counter_example_found) - continue; - } - } - log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0, - i, log_id(cell), log_id(cell->type), log_id(module)); - - initvals.remove_init(ff.sig_q[i]); - module->connect(ff.sig_q[i], val); - removed_sigbits.insert(i); - } - if (!removed_sigbits.empty()) { - std::vector<int> keep_bits; - for (int i = 0; i < ff.width; i++) - if (!removed_sigbits.count(i)) - keep_bits.push_back(i); - if (keep_bits.empty()) { - module->remove(cell); - did_something = true; - continue; - } - ff = ff.slice(keep_bits); - ff.cell = cell; - changed = true; - } - // The cell has been simplified as much as possible already. Now try to spice it up with enables / sync resets. if (ff.has_clk) { if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_ce || ff.ce_over_srst) && !opt.nosdff) { @@ -818,6 +721,115 @@ struct OptDffWorker } return did_something; } + + bool run_constbits() { + ModWalker modwalker(module->design, module); + QuickConeSat qcsat(modwalker); + + // Run as a separate sub-pass, so that we don't mutate (non-FF) cells under ModWalker. + bool did_something = false; + for (auto cell : module->selected_cells()) { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + FfData ff(&initvals, cell); + + // Now check if any bit can be replaced by a constant. + pool<int> removed_sigbits; + for (int i = 0; i < ff.width; i++) { + State val = ff.val_init[i]; + if (ff.has_arst) + val = combine_const(val, ff.val_arst[i]); + if (ff.has_srst) + val = combine_const(val, ff.val_srst[i]); + if (ff.has_sr) { + if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1)) + val = combine_const(val, State::S0); + if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1)) + val = combine_const(val, State::S1); + } + if (val == State::Sm) + continue; + if (ff.has_clk || ff.has_gclk) { + if (!ff.sig_d[i].wire) { + val = combine_const(val, ff.sig_d[i].data); + if (val == State::Sm) + continue; + } else { + if (!opt.sat) + continue; + // For each register bit, try to prove that it cannot change from the initial value. If so, remove it + if (!modwalker.has_drivers(ff.sig_d.extract(i))) + continue; + if (val != State::S0 && val != State::S1) + continue; + + int init_sat_pi = qcsat.importSigBit(val); + int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); + int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]); + + qcsat.prepare(); + + // Try to find out whether the register bit can change under some circumstances + bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); + + // If the register bit cannot change, we can replace it with a constant + if (counter_example_found) + continue; + } + } + if (ff.has_aload) { + if (!ff.sig_ad[i].wire) { + val = combine_const(val, ff.sig_ad[i].data); + if (val == State::Sm) + continue; + } else { + if (!opt.sat) + continue; + // For each register bit, try to prove that it cannot change from the initial value. If so, remove it + if (!modwalker.has_drivers(ff.sig_ad.extract(i))) + continue; + if (val != State::S0 && val != State::S1) + continue; + + int init_sat_pi = qcsat.importSigBit(val); + int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); + int d_sat_pi = qcsat.importSigBit(ff.sig_ad[i]); + + qcsat.prepare(); + + // Try to find out whether the register bit can change under some circumstances + bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); + + // If the register bit cannot change, we can replace it with a constant + if (counter_example_found) + continue; + } + } + log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0, + i, log_id(cell), log_id(cell->type), log_id(module)); + + initvals.remove_init(ff.sig_q[i]); + module->connect(ff.sig_q[i], val); + removed_sigbits.insert(i); + } + if (!removed_sigbits.empty()) { + std::vector<int> keep_bits; + for (int i = 0; i < ff.width; i++) + if (!removed_sigbits.count(i)) + keep_bits.push_back(i); + if (keep_bits.empty()) { + module->remove(cell); + did_something = true; + continue; + } + ff = ff.slice(keep_bits); + ff.cell = cell; + ff.emit(); + did_something = true; + } + } + return did_something; + } }; struct OptDffPass : public Pass { @@ -894,6 +906,8 @@ struct OptDffPass : public Pass { OptDffWorker worker(opt, mod); if (worker.run()) did_something = true; + if (worker.run_constbits()) + did_something = true; } if (did_something) diff --git a/passes/opt/opt_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/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index a292941c8..f37e07a89 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -40,7 +40,10 @@ struct Clk2fflogicPass : public Pass { log("\n"); } SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity) { - Wire *past_sig = module->addWire(NEW_ID, GetSize(sig)); + return wrap_async_control(module, sig, polarity, NEW_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)); module->addFf(NEW_ID, sig, past_sig); if (polarity) sig = module->Or(NEW_ID, sig, past_sig); @@ -105,7 +108,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 +124,13 @@ struct Clk2fflogicPass : public Pass { SigSpec clock_edge = module->Eqx(NEW_ID, {port.clk, SigSpec(past_clk)}, clock_edge_pattern); - SigSpec en_q = module->addWire(NEW_ID, GetSize(port.en)); + SigSpec en_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#en_q", log_id(mem.memid), i)), GetSize(port.en)); module->addFf(NEW_ID, port.en, en_q); - SigSpec addr_q = module->addWire(NEW_ID, GetSize(port.addr)); + SigSpec addr_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#addr_q", log_id(mem.memid), i)), GetSize(port.addr)); module->addFf(NEW_ID, port.addr, addr_q); - SigSpec data_q = module->addWire(NEW_ID, GetSize(port.data)); + SigSpec data_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#data_q", log_id(mem.memid), i)), GetSize(port.data)); module->addFf(NEW_ID, port.data, data_q); port.clk = State::S0; @@ -170,7 +173,14 @@ struct Clk2fflogicPass : public Pass { ff.remove(); - Wire *past_q = module->addWire(NEW_ID, ff.width); + // Strip spaces from signal name, since Yosys IDs can't contain spaces + // Spaces only occur when we have a signal that's a slice of a larger bus, + // e.g. "\myreg [5:0]", so removing spaces shouldn't result in loss of uniqueness + std::string sig_q_str = log_signal(ff.sig_q); + sig_q_str.erase(std::remove(sig_q_str.begin(), sig_q_str.end(), ' '), sig_q_str.end()); + + Wire *past_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_q_wire", sig_q_str.c_str())), ff.width); + if (!ff.is_fine) { module->addFf(NEW_ID, ff.sig_q, past_q); } else { @@ -182,7 +192,7 @@ struct Clk2fflogicPass : public Pass { if (ff.has_clk) { ff.unmap_ce_srst(); - Wire *past_clk = module->addWire(NEW_ID); + Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_clk#%s", sig_q_str.c_str(), log_signal(ff.sig_clk)))); initvals.set_init(past_clk, ff.pol_clk ? State::S1 : State::S0); if (!ff.is_fine) @@ -202,7 +212,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 @@ -241,7 +251,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, id); if (!ff.is_fine) module->addMux(NEW_ID, qval, ff.val_arst, arst, ff.sig_q); else diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 4e158da62..8081ffffe 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -21,19 +21,76 @@ #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; }; void zinit(State &v) @@ -51,7 +108,8 @@ void zinit(Const &v) struct SimInstance { SimShared *shared; - + + std::string scope; Module *module; Cell *instance; @@ -70,8 +128,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 +154,11 @@ 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; - 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 +180,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 +215,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,10 +228,15 @@ 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; } @@ -177,11 +253,10 @@ struct SimInstance { for (auto &it : ff_database) { - Cell *cell = it.first; ff_state_t &ff = it.second; zinit(ff.past_d); - 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 +328,16 @@ 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 < GetSize(data)) + state.data.bits[i+offset] = data.bits[i]; + } + void update_cell(Cell *cell) { if (ff_database.count(cell)) @@ -313,6 +398,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 +499,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 +612,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 +678,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 +708,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->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()); + + 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_vcd_header(f, id); + child.second->write_output_header(enter_scope, exit_scope, register_signal); - f << stringf("$upscope $end\n"); + 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 +762,134 @@ struct SimInstance continue; it.second.second = value; + data->emplace(id, value); + } - 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"; + for (auto child : children) + child.second->register_output_step_values(data); + } + + void setInitState() + { + for (auto &it : ff_database) + { + SigSpec qsig = it.second.data.sig_q; + if (qsig.is_wire()) { + IdString name = qsig.as_wire()->name; + fstHandle id = shared->fst->getHandle(scope + "." + RTLIL::unescape_id(name)); + if (id==0 && name.isPublic()) + log_warning("Unable to find wire %s in input file.\n", (scope + "." + RTLIL::unescape_id(name)).c_str()); + if (id!=0) { + Const fst_val = Const::from_string(shared->fst->valueOf(id)); + set_state(qsig, fst_val); } } + } + for (auto child : children) + child.second->setInitState(); + } - 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; + } } + } + 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 +930,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 +946,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,16 +974,812 @@ struct SimWorker : SimShared } update(); - write_vcd_step(10*cycle + 10); + register_output_step(10*cycle + 10); } - write_vcd_step(10*numcycles + 2); + 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); + } + + 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; + } + } + + 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) { + 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; + } + + void run_cosim_aiger_witness(Module *topmod) + { + log_assert(top == nullptr); + if ((clock.size()+clockn.size())==0) + log_error("Clock signal must be specified.\n"); + std::ifstream mf(map_filename); + std::string type, symbol; + int variable, index; + dict<int, std::pair<SigBit,bool>> inputs, inits, 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) + log_error("Wire %s not present in module %s\n",log_signal(w),log_id(topmod)); + 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; + top = new SimInstance(this, scope, topmod); + register_signals(); + + while (!f.eof()) + { + std::string line; + std::getline(f, line); + if (line.size()==0 || line[0]=='#') 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]!='c') { + // 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); + 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); + set_inports(clock, State::S0); + set_inports(clockn, State::S1); + } + update(); + register_output_step(10*cycle); + if (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 ((clock.size()+clockn.size())==0) + log_error("Clock signal must be specified.\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); + 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; + 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); + } else if (type == "init") { + aiw_inits[variable] = SigBit(w,index-w->start_offset); + } 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& d : worker->output_data) + { + for (auto &data : d.second) + { + current[data.first] = data.second; + } + if (first) { + for (int i = 0;; i++) + { + if (aiw_latches.count(i)) { + SigBit bit = aiw_latches.at(i).first; + auto v = current[mapping[bit.wire]].bits.at(bit.offset); + if (v == State::S1) + aiwfile << (aiw_latches.at(i).second ? '0' : '1'); + else + aiwfile << (aiw_latches.at(i).second ? '1' : '0'); + continue; + } + aiwfile << '\n'; + break; + } + first = false; + } + + for (int i = 0;; 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 << '\n'; + break; + } + } + } + + std::ofstream aiwfile; + dict<int, std::pair<SigBit, bool>> aiw_latches; + dict<int, SigBit> aiw_inputs, aiw_inits; + std::map<Wire*,int> mapping; }; struct SimPass : public Pass { @@ -767,6 +1795,19 @@ 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"); @@ -789,22 +1830,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 +1896,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 +1944,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 +1960,69 @@ 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; + } 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 +2038,139 @@ struct SimPass : public Pass { top_mod = mods.front(); } - worker.run(top_mod, numcycles); + if (worker.sim_filename.empty()) + worker.run(top_mod, numcycles); + else { + std::string filename_trim = file_base_name(worker.sim_filename); + if (filename_trim.size() > 4 && ((filename_trim.compare(filename_trim.size()-4, std::string::npos, ".fst") == 0) || + filename_trim.compare(filename_trim.size()-4, std::string::npos, ".vcd") == 0)) { + worker.run_cosim_fst(top_mod, numcycles); + } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".aiw") == 0) { + if (worker.map_filename.empty()) + log_cmd_error("For AIGER witness file map parameter is mandatory.\n"); + worker.run_cosim_aiger_witness(top_mod); + } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) { + worker.run_cosim_btor2_witness(top_mod); + } else { + log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str()); + } + } } } SimPass; +struct Fst2TbPass : public Pass { + Fst2TbPass() : Pass("fst2tb", "generate testbench out of fst file") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" fst2tb [options] [top-level]\n"); + log("\n"); + log("This command generates testbench for the circuit using the given top-level module\n"); + log("and simulus signal from FST file\n"); + log("\n"); + log(" -tb <name>\n"); + log(" generated testbench name.\n"); + log(" files <name>.v and <name>.txt are created as result.\n"); + log("\n"); + log(" -r <filename>\n"); + log(" read simulation FST file\n"); + log("\n"); + log(" -clock <portname>\n"); + log(" name of top-level clock input\n"); + log("\n"); + log(" -clockn <portname>\n"); + log(" name of top-level clock input (inverse polarity)\n"); + log("\n"); + log(" -scope <name>\n"); + log(" scope of simulation top model\n"); + log("\n"); + log(" -start <time>\n"); + log(" start co-simulation in arbitary time (default 0)\n"); + log("\n"); + log(" -stop <time>\n"); + log(" stop co-simulation in arbitary time (default END)\n"); + log("\n"); + log(" -n <integer>\n"); + log(" number of clock cycles to simulate (default: 20)\n"); + log("\n"); + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + SimWorker worker; + int numcycles = 20; + bool stop_set = false; + std::string tb_filename; + + log_header(design, "Executing FST2FB pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-clock" && argidx+1 < args.size()) { + worker.clock.insert(RTLIL::escape_id(args[++argidx])); + continue; + } + if (args[argidx] == "-clockn" && argidx+1 < args.size()) { + worker.clockn.insert(RTLIL::escape_id(args[++argidx])); + continue; + } + if (args[argidx] == "-r" && argidx+1 < args.size()) { + std::string sim_filename = args[++argidx]; + rewrite_filename(sim_filename); + worker.sim_filename = sim_filename; + continue; + } + if (args[argidx] == "-n" && argidx+1 < args.size()) { + numcycles = atoi(args[++argidx].c_str()); + worker.cycles_set = true; + continue; + } + if (args[argidx] == "-scope" && argidx+1 < args.size()) { + worker.scope = args[++argidx]; + continue; + } + if (args[argidx] == "-start" && argidx+1 < args.size()) { + worker.start_time = stringToTime(args[++argidx]); + continue; + } + if (args[argidx] == "-stop" && argidx+1 < args.size()) { + worker.stop_time = stringToTime(args[++argidx]); + stop_set = true; + continue; + } + if (args[argidx] == "-tb" && argidx+1 < args.size()) { + tb_filename = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + if (stop_set && worker.cycles_set) + log_error("'stop' and 'n' can only be used exclusively'\n"); + + Module *top_mod = nullptr; + + if (design->full_selection()) { + top_mod = design->top_module(); + + if (!top_mod) + log_cmd_error("Design has no top module, use the 'hierarchy' command to specify one.\n"); + } else { + auto mods = design->selected_whole_modules(); + if (GetSize(mods) != 1) + log_cmd_error("Only one top module must be selected.\n"); + top_mod = mods.front(); + } + + if (tb_filename.empty()) + log_cmd_error("Testbench name must be defined.\n"); + + if (worker.sim_filename.empty()) + log_cmd_error("Stimulus FST file must be defined.\n"); + + worker.generate_tb(top_mod, tb_filename, numcycles); + } +} Fst2TbPass; + PRIVATE_NAMESPACE_END diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 035699603..98ccfc303 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -29,6 +29,8 @@ OBJS += passes/techmap/extract_reduce.o OBJS += passes/techmap/alumacc.o OBJS += passes/techmap/dffinit.o OBJS += passes/techmap/pmuxtree.o +OBJS += passes/techmap/bmuxmap.o +OBJS += passes/techmap/demuxmap.o OBJS += passes/techmap/muxcover.o OBJS += passes/techmap/aigmap.o OBJS += passes/techmap/tribuf.o diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 80c6282c4..2c91f3a48 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -757,10 +757,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)) diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc index 29fe74ec7..b8975f178 100644 --- a/passes/techmap/abc9_ops.cc +++ b/passes/techmap/abc9_ops.cc @@ -167,10 +167,6 @@ 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); } @@ -180,7 +176,16 @@ 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 diff --git a/passes/techmap/bmuxmap.cc b/passes/techmap/bmuxmap.cc new file mode 100644 index 000000000..03673c278 --- /dev/null +++ b/passes/techmap/bmuxmap.cc @@ -0,0 +1,76 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina KoĆcielnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BmuxmapPass : public Pass { + BmuxmapPass() : Pass("bmuxmap", "transform $bmux cells to trees of $mux cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" bmuxmap [selection]\n"); + log("\n"); + log("This pass transforms $bmux cells to trees of $mux cells.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing BMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($bmux)) + continue; + + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + int width = GetSize(cell->getPort(ID::Y)); + + for (int idx = 0; idx < GetSize(sel); idx++) { + SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); + for (int i = 0; i < GetSize(new_data); i += width) { + RTLIL::Cell *mux = module->addMux(NEW_ID, + data.extract(i*2, width), + data.extract(i*2+width, width), + sel[idx], + new_data.extract(i, width)); + mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } + data = new_data; + } + + module->connect(cell->getPort(ID::Y), data); + module->remove(cell); + } + } +} BmuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/demuxmap.cc b/passes/techmap/demuxmap.cc new file mode 100644 index 000000000..292b18bad --- /dev/null +++ b/passes/techmap/demuxmap.cc @@ -0,0 +1,80 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina KoĆcielnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct DemuxmapPass : public Pass { + DemuxmapPass() : Pass("demuxmap", "transform $demux cells to $eq + $mux cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" demuxmap [selection]\n"); + log("\n"); + log("This pass transforms $demux cells to a bunch of equality comparisons.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing DEMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($demux)) + continue; + + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + SigSpec out = cell->getPort(ID::Y); + int width = GetSize(cell->getPort(ID::A)); + + for (int i = 0; i < 1 << GetSize(sel); i++) { + if (width == 1 && data == State::S1) { + RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), out[i]); + eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } else { + Wire *eq = module->addWire(NEW_ID); + RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), eq); + eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + RTLIL::Cell *mux = module->addMux(NEW_ID, + Const(State::S0, width), + data, + eq, + out.extract(i*width, width)); + mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } + } + + module->remove(cell); + } + } +} DemuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/iopadmap.cc b/passes/techmap/iopadmap.cc index 990e28876..437ad5156 100644 --- a/passes/techmap/iopadmap.cc +++ b/passes/techmap/iopadmap.cc @@ -46,7 +46,7 @@ struct IopadmapPass : public Pass { log(" -inpad <celltype> <in_port>[:<ext_port>]\n"); log(" Map module input ports to the given cell type with the\n"); log(" given output port name. if a 2nd portname is given, the\n"); - log(" signal is passed through the pad call, using the 2nd\n"); + log(" signal is passed through the pad cell, using the 2nd\n"); log(" portname as the port facing the module port.\n"); log("\n"); log(" -outpad <celltype> <out_port>[:<ext_port>]\n"); diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index 68f44cf6d..7d8dba439 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -299,6 +299,30 @@ void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell) } } +void simplemap_bmux(RTLIL::Module *module, RTLIL::Cell *cell) +{ + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + int width = GetSize(cell->getPort(ID::Y)); + + for (int idx = 0; idx < GetSize(sel); idx++) { + SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); + for (int i = 0; i < GetSize(new_data); i += width) { + for (int k = 0; k < width; k++) { + RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); + gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->setPort(ID::A, data[i*2+k]); + gate->setPort(ID::B, data[i*2+width+k]); + gate->setPort(ID::S, sel[idx]); + gate->setPort(ID::Y, new_data[i+k]); + } + } + data = new_data; + } + + module->connect(cell->getPort(ID::Y), data); +} + void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell) { SigSpec lut_ctrl = cell->getPort(ID::A); @@ -306,7 +330,6 @@ void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell) lut_data.extend_u0(1 << cell->getParam(ID::WIDTH).as_int()); for (int idx = 0; GetSize(lut_data) > 1; idx++) { - SigSpec sig_s = lut_ctrl[idx]; SigSpec new_lut_data = module->addWire(NEW_ID, GetSize(lut_data)/2); for (int i = 0; i < GetSize(lut_data); i += 2) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); @@ -400,6 +423,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($nex)] = simplemap_eqne; mappers[ID($mux)] = simplemap_mux; mappers[ID($tribuf)] = simplemap_tribuf; + mappers[ID($bmux)] = simplemap_bmux; mappers[ID($lut)] = simplemap_lut; mappers[ID($sop)] = simplemap_sop; mappers[ID($slice)] = simplemap_slice; diff --git a/passes/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)] = "*"; } |