diff options
Diffstat (limited to 'passes')
55 files changed, 9725 insertions, 2135 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 53bfd40c6..16a38b511 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -18,6 +18,7 @@ OBJS += passes/cmds/setattr.o OBJS += passes/cmds/copy.o OBJS += passes/cmds/splice.o OBJS += passes/cmds/scc.o +OBJS += passes/cmds/glift.o OBJS += passes/cmds/torder.o OBJS += passes/cmds/logcmd.o OBJS += passes/cmds/tee.o @@ -40,3 +41,5 @@ endif OBJS += passes/cmds/scratchpad.o OBJS += passes/cmds/logger.o OBJS += passes/cmds/printattrs.o +OBJS += passes/cmds/sta.o +OBJS += passes/cmds/clean_zerowidth.o diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 16ac5b6a7..7b621504d 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -377,7 +377,7 @@ struct BugpointPass : public Pass { if (wire->get_bool_attribute(ID::bugpoint_keep)) continue; - if (wire->name.begins_with("$delete_wire")) + if (wire->name.begins_with("$delete_wire") || wire->name.begins_with("$auto$bugpoint")) continue; if (index++ == seed) diff --git a/passes/cmds/clean_zerowidth.cc b/passes/cmds/clean_zerowidth.cc new file mode 100644 index 000000000..bac6b1521 --- /dev/null +++ b/passes/cmds/clean_zerowidth.cc @@ -0,0 +1,210 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2021 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/celltypes.h" +#include "kernel/mem.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct CleanZeroWidthPass : public Pass { + CleanZeroWidthPass() : Pass("clean_zerowidth", "clean zero-width connections from the design") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" clean_zerowidth [selection]\n"); + log("\n"); + log("Fixes the selected cells and processes to contain no zero-width connections.\n"); + log("Depending on the cell type, this may be implemented by removing the connection,\n"); + log("widening it to 1-bit, or removing the cell altogether.\n"); + log("\n"); + } + + void clean_case(RTLIL::CaseRule *cs) + { + std::vector<SigSig> new_actions; + for (auto &action : cs->actions) + if (GetSize(action.first) != 0) + new_actions.push_back(action); + std::swap(new_actions, cs->actions); + for (auto sw : cs->switches) + for (auto scs : sw->cases) + clean_case(scs); + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + break; + } + extra_args(args, argidx, design); + + CellTypes ct; + ct.setup(); + + for (auto module : design->selected_modules()) + { + for (auto cell : module->selected_cells()) + { + if (!ct.cell_known(cell->type)) { + // User-defined cell: just prune zero-width connections. + for (auto it: cell->connections()) { + if (GetSize(it.second) == 0) { + cell->unsetPort(it.first); + } + } + } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + // Coarse FF cells: remove if WIDTH == 0 (no outputs). + // This will also trigger on fine cells, so use the Q port + // width instead of actual WIDTH parameter. + if (GetSize(cell->getPort(ID::Q)) == 0) { + module->remove(cell); + } + } else if (cell->type.in(ID($pmux), ID($bmux), ID($demux))) { + // Remove altogether if WIDTH is 0, replace with + // a connection if S_WIDTH is 0. + if (cell->getParam(ID::WIDTH).as_int() == 0) { + module->remove(cell); + } + if (cell->getParam(ID::S_WIDTH).as_int() == 0) { + module->connect(cell->getPort(ID::Y), cell->getPort(ID::A)); + module->remove(cell); + } + } else if (cell->type == ID($concat)) { + // If a concat has a zero-width input: replace with direct + // connection to the other input. + if (cell->getParam(ID::A_WIDTH).as_int() == 0) { + module->connect(cell->getPort(ID::Y), cell->getPort(ID::B)); + module->remove(cell); + } else if (cell->getParam(ID::B_WIDTH).as_int() == 0) { + module->connect(cell->getPort(ID::Y), cell->getPort(ID::A)); + module->remove(cell); + } + } else if (cell->type == ID($fsm)) { + // TODO: not supported + } else if (cell->is_mem_cell()) { + // Skip — will be handled below. + } else if (cell->type == ID($lut)) { + // Zero-width LUT is just a const driver. + if (cell->getParam(ID::WIDTH).as_int() == 0) { + module->connect(cell->getPort(ID::Y), cell->getParam(ID::LUT)[0]); + module->remove(cell); + } + } else if (cell->type == ID($sop)) { + // Zero-width SOP is just a const driver. + if (cell->getParam(ID::WIDTH).as_int() == 0) { + // The value is 1 iff DEPTH is non-0. + bool val = cell->getParam(ID::DEPTH).as_int() != 0; + module->connect(cell->getPort(ID::Y), val); + module->remove(cell); + } + } else if (cell->hasParam(ID::WIDTH)) { + // For cells with WIDTH parameter: remove if zero. + if (cell->getParam(ID::WIDTH).as_int() == 0) { + module->remove(cell); + } + } else if (cell->hasParam(ID::Y_WIDTH)) { + // For most operators: remove if Y width is 0, expand + // A and B to 1-bit if their width is 0. + if (cell->getParam(ID::Y_WIDTH).as_int() == 0) { + module->remove(cell); + } else if (cell->type == ID($macc)) { + // TODO: fixing zero-width A and B not supported. + } else { + if (cell->getParam(ID::A_WIDTH).as_int() == 0) { + cell->setPort(ID::A, State::S0); + cell->setParam(ID::A_WIDTH, 1); + } + if (cell->hasParam(ID::B_WIDTH) && cell->getParam(ID::B_WIDTH).as_int() == 0) { + cell->setPort(ID::B, State::S0); + cell->setParam(ID::B_WIDTH, 1); + } + } + } + } + + // NOTE: Zero-width switch signals are left alone for processes, as there + // is no simple way of cleaning them up. + for (auto &it: module->processes) { + if (!design->selected(module, it.second)) + continue; + clean_case(&it.second->root_case); + for (auto sync : it.second->syncs) { + std::vector<int> swizzle; + std::vector<RTLIL::MemWriteAction> new_memwr_actions; + for (int i = 0; i < GetSize(sync->mem_write_actions); i++) { + auto &memwr = sync->mem_write_actions[i]; + if (GetSize(memwr.data) == 0) + continue; + if (GetSize(memwr.address) == 0) + memwr.address = State::S0; + Const priority_mask; + for (auto x : swizzle) { + priority_mask.bits.push_back(memwr.priority_mask.bits[x]); + } + memwr.priority_mask = priority_mask; + swizzle.push_back(i); + new_memwr_actions.push_back(memwr); + } + std::swap(new_memwr_actions, sync->mem_write_actions); + std::vector<SigSig> new_actions; + for (auto &action : sync->actions) + if (GetSize(action.first) != 0) + new_actions.push_back(action); + std::swap(new_actions, sync->actions); + } + } + + for (auto &mem : Mem::get_selected_memories(module)) { + if (mem.width == 0) { + mem.remove(); + continue; + } + for (auto &init : mem.inits) { + if (GetSize(init.addr) == 0) { + init.addr = State::S0; + } + } + for (auto &port : mem.rd_ports) { + if (GetSize(port.addr) == 0) { + port.addr = State::S0; + } + } + for (auto &port : mem.wr_ports) { + if (GetSize(port.addr) == 0) { + port.addr = State::S0; + } + } + mem.emit(); + } + + std::vector<SigSig> new_conns; + for (auto &conn : module->connections()) + if (GetSize(conn.first) != 0) + new_conns.push_back(conn); + module->new_connections(new_conns); + } + } +} CleanZeroWidthPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc new file mode 100644 index 000000000..b398c3e04 --- /dev/null +++ b/passes/cmds/glift.cc @@ -0,0 +1,599 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 Alberto Gonzalez <boqwxp@airmail.cc> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/utils.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct GliftWorker { +private: + bool is_top_module = false; + bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false; + bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false; + bool opt_instrumentmore = false; + std::vector<RTLIL::Wire *> new_taint_outputs; + std::vector<std::pair<RTLIL::SigSpec, RTLIL::IdString>> meta_mux_selects; + RTLIL::Module *module = nullptr; + + const RTLIL::IdString cost_model_wire_name = ID(__glift_weight); + const RTLIL::IdString glift_attribute_name = ID(glift); + + + RTLIL::SigSpec get_corresponding_taint_signal(RTLIL::SigSpec sig) { + RTLIL::SigSpec ret; + + //Get the connected wire for the cell port: + log_assert(sig.is_wire() || sig.is_fully_const()); + log_assert(sig.is_wire() || sig.is_fully_const()); + + //Get a SigSpec for the corresponding taint signal for the cell port, creating one if necessary: + if (sig.is_wire()) { + RTLIL::Wire *w = module->wire(sig.as_wire()->name.str() + "_t"); + if (w == nullptr) w = module->addWire(sig.as_wire()->name.str() + "_t", 1); + ret = w; + } + else if (sig.is_fully_const() && opt_taintconstants) + ret = RTLIL::State::S1; + else if (sig.is_fully_const()) + ret = RTLIL::State::S0; + else + log_cmd_error("Cell port SigSpec has unexpected type.\n"); + + //Finally, if the cell port was a module input or output, make sure the corresponding taint signal is marked, too: + if(sig.is_wire() && sig.as_wire()->port_input) + ret.as_wire()->port_input = true; + if(sig.is_wire() && sig.as_wire()->port_output) + new_taint_outputs.push_back(ret.as_wire()); + + return ret; + } + + void add_precise_GLIFT_logic(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH2 or OR2_SH2 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_1_1", port_a, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_1_2", port_b, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_1_3", is_and? port_a : n_port_a, port_b_taint, false, cell->get_src_attribute()); + auto subexpr2 = module->And(cell->name.str() + "_t_1_4", is_and? port_b : n_port_b, port_a_taint, false, cell->get_src_attribute()); + auto subexpr3 = module->And(cell->name.str() + "_t_1_5", port_a_taint, port_b_taint, false, cell->get_src_attribute()); + auto subexpr4 = module->Or(cell->name.str() + "_t_1_6", subexpr1, subexpr2, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_1_7", subexpr4, subexpr3, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_1(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH3 or OR2_SH3 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_2_1", port_a, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_2_2", is_and? port_b : n_port_a, is_and? port_a_taint : port_b_taint, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_2_3", is_and? port_b_taint : port_a_taint, subexpr1, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_2(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH4 or OR2_SH4 + bool is_and = cell->type.in(ID($_AND_), ID($_NAND_)); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_3_1", port_b, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_3_2", is_and? port_a : n_port_b, is_and? port_b_taint : port_a_taint, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_3_3", is_and? port_a_taint : port_b_taint, subexpr1, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_3(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + //AKA AN2_SH5 or OR2_SH5 or XR2_SH2 + module->addOr(cell->name.str() + "_t_4_1", port_a_taint, port_b_taint, port_y_taint, false, cell->get_src_attribute()); + } + + void add_imprecise_GLIFT_logic_4(RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, port_a_taint); + } + + void add_imprecise_GLIFT_logic_5(RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, port_b_taint); + } + + void add_imprecise_GLIFT_logic_6(RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, RTLIL::Const(1, 1)); + } + + void add_imprecise_GLIFT_logic_7(RTLIL::SigSpec &port_y_taint) { + module->connect(port_y_taint, RTLIL::Const(0, 1)); + } + + void add_precise_GLIFT_mux(const RTLIL::Cell *cell, RTLIL::SigSpec &port_a, RTLIL::SigSpec &port_a_taint, RTLIL::SigSpec &port_b, RTLIL::SigSpec &port_b_taint, RTLIL::SigSpec &port_s, RTLIL::SigSpec &port_s_taint, RTLIL::SigSpec &port_y_taint) { + //S&At | ~S&Bt | ~A&B&St | A&~B&St | At&St | Bt&St + RTLIL::SigSpec n_port_a = module->LogicNot(cell->name.str() + "_t_4_1", port_a, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_b = module->LogicNot(cell->name.str() + "_t_4_2", port_b, false, cell->get_src_attribute()); + RTLIL::SigSpec n_port_s = module->LogicNot(cell->name.str() + "_t_4_3", port_s, false, cell->get_src_attribute()); + auto subexpr1 = module->And(cell->name.str() + "_t_4_4", port_s, port_a_taint, false, cell->get_src_attribute()); + auto subexpr2 = module->And(cell->name.str() + "_t_4_5", n_port_s, port_b_taint, false, cell->get_src_attribute()); + auto subexpr3 = module->And(cell->name.str() + "_t_4_6", n_port_a, port_b, false, cell->get_src_attribute()); + auto subexpr4 = module->And(cell->name.str() + "_t_4_7", subexpr3, port_s_taint, false, cell->get_src_attribute()); + auto subexpr5 = module->And(cell->name.str() + "_t_4_8", port_a, n_port_b, false, cell->get_src_attribute()); + auto subexpr6 = module->And(cell->name.str() + "_t_4_9", subexpr5, port_s_taint, false, cell->get_src_attribute()); + auto subexpr7 = module->And(cell->name.str() + "_t_4_10", port_a_taint, port_s_taint, false, cell->get_src_attribute()); + auto subexpr8 = module->And(cell->name.str() + "_t_4_11", port_b_taint, port_s_taint, false, cell->get_src_attribute()); + auto subexpr9 = module->Or(cell->name.str() + "_t_4_12", subexpr1, subexpr2, false, cell->get_src_attribute()); + auto subexpr10 = module->Or(cell->name.str() + "_t_4_13", subexpr4, subexpr6, false, cell->get_src_attribute()); + auto subexpr11 = module->Or(cell->name.str() + "_t_4_14", subexpr7, subexpr8, false, cell->get_src_attribute()); + auto subexpr12 = module->Or(cell->name.str() + "_t_4_15", subexpr9, subexpr10, false, cell->get_src_attribute()); + module->addOr(cell->name.str() + "_t_4_16", subexpr11, subexpr12, port_y_taint, false, cell->get_src_attribute()); + } + + RTLIL::SigSpec score_metamux_select(const RTLIL::SigSpec &metamux_select, const RTLIL::IdString celltype) { + log_assert(metamux_select.is_wire()); + + if (opt_simplecostmodel) { + //The complex model is an area model, so a lower score should mean smaller. + //In this case, a nonzero hole metamux select value means less logic. + //Thus we should invert the ReduceOr over the metamux_select signal. + RTLIL::SigSpec pmux_select = module->ReduceOr(metamux_select.as_wire()->name.str() + "_nonzero", metamux_select); + return module->Pmux(NEW_ID, RTLIL::Const(1), RTLIL::Const(0), pmux_select, metamux_select.as_wire()->get_src_attribute()); + } else { + auto select_width = metamux_select.as_wire()->width; + + std::vector<RTLIL::Const> costs; + if (celltype == ID($_AND_) || celltype == ID($_OR_)) { + costs = {5, 2, 2, 1, 0, 0, 0, 0}; + log_assert(select_width == 2 || select_width == 3); + log_assert(opt_instrumentmore || select_width == 2); + log_assert(!opt_instrumentmore || select_width == 3); + } + else if (celltype == ID($_XOR_) || celltype == ID($_XNOR_)) { + costs = {1, 0, 0, 0}; + log_assert(select_width == 2); + } + + std::vector<RTLIL::SigSpec> next_pmux_y_ports, pmux_y_ports(costs.begin(), costs.begin() + exp2(select_width)); + for (auto i = 0; pmux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(pmux_y_ports); j += 2) { + next_pmux_y_ports.emplace_back(module->Pmux(stringf("%s_mux_%d_%d", metamux_select.as_wire()->name.c_str(), i, j), pmux_y_ports[j], pmux_y_ports[j+1], metamux_select[GetSize(metamux_select) - 1 - i], metamux_select.as_wire()->get_src_attribute())); + } + if (GetSize(pmux_y_ports) % 2 == 1) + next_pmux_y_ports.push_back(pmux_y_ports[GetSize(pmux_y_ports) - 1]); + pmux_y_ports.swap(next_pmux_y_ports); + next_pmux_y_ports.clear(); + } + + log_assert(pmux_y_ports.size() == 1); + return pmux_y_ports[0]; + } + } + + void create_glift_logic() { + if (module->get_bool_attribute(glift_attribute_name)) + return; + + std::vector<RTLIL::SigSig> connections(module->connections()); + + for(auto &cell : module->cells().to_vector()) { + if (!cell->type.in({ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_MUX_), ID($_NMUX_), ID($_NOT_), ID($anyconst), ID($allconst), ID($assume), ID($assert)}) && module->design->module(cell->type) == nullptr) { + log_cmd_error("Unsupported cell type \"%s\" found. Run `techmap` first.\n", cell->type.c_str()); + } + if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_))) { + const unsigned int A = 0, B = 1, Y = 2; + const unsigned int NUM_PORTS = 3; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (opt_create_precise_model) + add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], port_taints[Y]); + else if (opt_create_imprecise_model) + add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]); + else if (opt_create_instrumented_model) { + std::vector<RTLIL::SigSpec> taint_version; + int num_versions = opt_instrumentmore? 8 : 4; + + for (auto i = 1; i <= num_versions; ++i) + taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1))); + + for (auto i = 0; i < num_versions; ++i) { + switch(i) { + case 0: add_precise_GLIFT_logic(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 1: add_imprecise_GLIFT_logic_1(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 2: add_imprecise_GLIFT_logic_2(cell, ports[A], port_taints[A], ports[B], port_taints[B], taint_version[i]); + break; + case 3: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]); + break; + case 4: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]); + break; + case 5: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]); + break; + case 6: add_imprecise_GLIFT_logic_6(taint_version[i]); + break; + case 7: add_imprecise_GLIFT_logic_7(taint_version[i]); + break; + default: log_assert(false); + } + } + + auto select_width = log2(num_versions); + log_assert(exp2(select_width) == num_versions); + RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width)); + meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type)); + module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute())); + + std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version); + for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) { + next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i])); + } + if (GetSize(meta_mux_y_ports) % 2 == 1) + next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]); + meta_mux_y_ports.swap(next_meta_mux_y_ports); + next_meta_mux_y_ports.clear(); + } + log_assert(meta_mux_y_ports.size() == 1); + module->connect(port_taints[Y], meta_mux_y_ports[0]); + } + else log_cmd_error("This is a bug (1).\n"); + } + else if (cell->type.in(ID($_XOR_), ID($_XNOR_))) { + const unsigned int A = 0, B = 1, Y = 2; + const unsigned int NUM_PORTS = 3; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (opt_create_precise_model || opt_create_imprecise_model) + add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], port_taints[Y]); + else if (opt_create_instrumented_model) { + std::vector<RTLIL::SigSpec> taint_version; + int num_versions = 4; + auto select_width = log2(num_versions); + log_assert(exp2(select_width) == num_versions); + + for (auto i = 1; i <= num_versions; ++i) + taint_version.emplace_back(RTLIL::SigSpec(module->addWire(stringf("%s_y%d", cell->name.c_str(), i), 1))); + + for (auto i = 0; i < num_versions; ++i) { + switch(i) { + case 0: add_imprecise_GLIFT_logic_3(cell, port_taints[A], port_taints[B], taint_version[i]); + break; + case 1: add_imprecise_GLIFT_logic_4(port_taints[A], taint_version[i]); + break; + case 2: add_imprecise_GLIFT_logic_5(port_taints[B], taint_version[i]); + break; + case 3: add_imprecise_GLIFT_logic_6(taint_version[i]); + break; + default: log_assert(false); + } + } + + RTLIL::SigSpec meta_mux_select(module->addWire(cell->name.str() + "_sel", select_width)); + meta_mux_selects.push_back(make_pair(meta_mux_select, cell->type)); + module->connect(meta_mux_select, module->Anyconst(cell->name.str() + "_hole", select_width, cell->get_src_attribute())); + + std::vector<RTLIL::SigSpec> next_meta_mux_y_ports, meta_mux_y_ports(taint_version); + for (auto i = 0; meta_mux_y_ports.size() > 1; ++i) { + for (auto j = 0; j+1 < GetSize(meta_mux_y_ports); j += 2) { + next_meta_mux_y_ports.emplace_back(module->Mux(stringf("%s_mux_%d_%d", cell->name.c_str(), i, j), meta_mux_y_ports[j], meta_mux_y_ports[j+1], meta_mux_select[GetSize(meta_mux_select) - 1 - i])); + } + if (GetSize(meta_mux_y_ports) % 2 == 1) + next_meta_mux_y_ports.push_back(meta_mux_y_ports[GetSize(meta_mux_y_ports) - 1]); + meta_mux_y_ports.swap(next_meta_mux_y_ports); + next_meta_mux_y_ports.clear(); + } + log_assert(meta_mux_y_ports.size() == 1); + module->connect(port_taints[Y], meta_mux_y_ports[0]); + } + else log_cmd_error("This is a bug (2).\n"); + + } + else if (cell->type.in(ID($_MUX_), ID($_NMUX_))) { + const unsigned int A = 0, B = 1, S = 2, Y = 3; + const unsigned int NUM_PORTS = 4; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::B), cell->getPort(ID::S), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[B].size() != 1 || ports[S].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + add_precise_GLIFT_mux(cell, ports[A], port_taints[A], ports[B], port_taints[B], ports[S], port_taints[S], port_taints[Y]); + } + else if (cell->type.in(ID($_NOT_))) { + const unsigned int A = 0, Y = 1; + const unsigned int NUM_PORTS = 2; + RTLIL::SigSpec ports[NUM_PORTS] = {cell->getPort(ID::A), cell->getPort(ID::Y)}; + RTLIL::SigSpec port_taints[NUM_PORTS]; + + if (ports[A].size() != 1 || ports[Y].size() != 1) + log_cmd_error("Multi-bit signal found. Run `splitnets` first.\n"); + for (unsigned int i = 0; i < NUM_PORTS; ++i) + port_taints[i] = get_corresponding_taint_signal(ports[i]); + + if (cell->type == ID($_NOT_)) { + module->connect(port_taints[Y], port_taints[A]); + } + else log_cmd_error("This is a bug (3).\n"); + } + else if (module->design->module(cell->type) != nullptr) { + //User cell type + //This function is called on modules according to topological order, so we do not need to + //recurse to GLIFT model the child module. However, we need to augment the ports list + //with taint signals and connect the new ports to the corresponding taint signals. + RTLIL::Module *cell_module_def = module->design->module(cell->type); + dict<RTLIL::IdString, RTLIL::SigSpec> orig_ports = cell->connections(); + log("Adding cell %s\n", cell_module_def->name.c_str()); + for (auto &it : orig_ports) { + RTLIL::SigSpec port = it.second; + RTLIL::SigSpec port_taint = get_corresponding_taint_signal(port); + + log_assert(port_taint.is_wire()); + log_assert(std::find(cell_module_def->ports.begin(), cell_module_def->ports.end(), port_taint.as_wire()->name) != cell_module_def->ports.end()); + cell->setPort(port_taint.as_wire()->name, port_taint); + } + } + else log_cmd_error("This is a bug (4).\n"); + } //end foreach cell in cells + + for (auto &conn : connections) { + RTLIL::SigSpec first = get_corresponding_taint_signal(conn.first); + RTLIL::SigSpec second = get_corresponding_taint_signal(conn.second); + + module->connect(first, second); + + if(conn.second.is_wire() && conn.second.as_wire()->port_input) + second.as_wire()->port_input = true; + if(conn.first.is_wire() && conn.first.as_wire()->port_output) + new_taint_outputs.push_back(first.as_wire()); + } //end foreach conn in connections + + //Create a rough model of area by summing the (potentially simplified) "weight" score of each meta-mux select: + if (!opt_nocostmodel) { + std::vector<RTLIL::SigSpec> meta_mux_select_sums; + std::vector<RTLIL::SigSpec> meta_mux_select_sums_buf; + for (auto &it : meta_mux_selects) { + meta_mux_select_sums.emplace_back(score_metamux_select(it.first, it.second)); + } + for (unsigned int i = 0; meta_mux_select_sums.size() > 1; ) { + meta_mux_select_sums_buf.clear(); + for (i = 0; i + 1 < meta_mux_select_sums.size(); i += 2) { + meta_mux_select_sums_buf.push_back(module->Add(meta_mux_select_sums[i].as_wire()->name.str() + "_add", meta_mux_select_sums[i], meta_mux_select_sums[i+1], false)); + } + if (meta_mux_select_sums.size() % 2 == 1) + meta_mux_select_sums_buf.push_back(meta_mux_select_sums[meta_mux_select_sums.size()-1]); + meta_mux_select_sums.swap(meta_mux_select_sums_buf); + } + if (meta_mux_select_sums.size() > 0) { + meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\minimize"); + meta_mux_select_sums[0].as_wire()->set_bool_attribute("\\keep"); + module->rename(meta_mux_select_sums[0].as_wire(), cost_model_wire_name); + } + } + + //Mark new module outputs: + for (auto &port_name : module->ports) { + RTLIL::Wire *port = module->wire(port_name); + log_assert(port != nullptr); + if (is_top_module && port->port_output && !opt_keepoutputs) + port->port_output = false; + } + for (auto &output : new_taint_outputs) + output->port_output = true; + module->fixup_ports(); //we have some new taint signals in the module interface + module->set_bool_attribute(glift_attribute_name, true); + } + +public: + GliftWorker(RTLIL::Module *_module, bool _is_top_module, bool _opt_create_precise_model, bool _opt_create_imprecise_model, bool _opt_create_instrumented_model, bool _opt_taintconstants, bool _opt_keepoutputs, bool _opt_simplecostmodel, bool _opt_nocostmodel, bool _opt_instrumentmore) { + module = _module; + is_top_module = _is_top_module; + opt_create_precise_model = _opt_create_precise_model; + opt_create_imprecise_model = _opt_create_imprecise_model; + opt_create_instrumented_model = _opt_create_instrumented_model; + opt_taintconstants = _opt_taintconstants; + opt_keepoutputs = _opt_keepoutputs; + opt_simplecostmodel = _opt_simplecostmodel; + opt_nocostmodel = _opt_nocostmodel; + opt_instrumentmore = _opt_instrumentmore; + + create_glift_logic(); + } +}; + +struct GliftPass : public Pass { + GliftPass() : Pass("glift", "create GLIFT models and optimization problems") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" glift <command> [options] [selection]\n"); + log("\n"); + log("Augments the current or specified module with gate-level information flow tracking\n"); + log("(GLIFT) logic using the \"constructive mapping\" approach. Also can set up QBF-SAT\n"); + log("optimization problems in order to optimize GLIFT models or trade off precision and\n"); + log("complexity.\n"); + log("\n"); + log("\n"); + log("Commands:\n"); + log("\n"); + log(" -create-precise-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with precise taint tracking logic.\n"); + log(" For example, precise taint tracking logic for an AND gate is:\n"); + log("\n"); + log(" y_t = a & b_t | b & a_t | a_t & b_t\n"); + log("\n"); + log("\n"); + log(" -create-imprecise-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with imprecise \"All OR\" taint tracking\n"); + log(" logic:\n"); + log("\n"); + log(" y_t = a_t | b_t\n"); + log("\n"); + log("\n"); + log(" -create-instrumented-model\n"); + log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); + log(" inputs, outputs, and internal nets along with 4 varying-precision versions of taint\n"); + log(" tracking logic. Which version of taint tracking logic is used for a given gate is\n"); + log(" determined by a MUX selected by an $anyconst cell. By default, unless the\n"); + log(" `-no-cost-model` option is provided, an additional wire named `__glift_weight` with\n"); + log(" the `keep` and `minimize` attributes is added to the module along with pmuxes and\n"); + log(" adders to calculate a rough estimate of the number of logic gates in the GLIFT model\n"); + log(" given an assignment for the $anyconst cells. The four versions of taint tracking logic\n"); + log(" for an AND gate are:"); + log("\n"); + log(" y_t = a & b_t | b & a_t | a_t & b_t (like `-create-precise-model`)\n"); + log(" y_t = a_t | a & b_t\n"); + log(" y_t = b_t | b & a_t\n"); + log(" y_t = a_t | b_t (like `-create-imprecise-model`)\n"); + log("\n"); + log("\n"); + log("Options:\n"); + log("\n"); + log(" -taint-constants\n"); + log(" Constant values in the design are labeled as tainted.\n"); + log(" (default: label constants as un-tainted)\n"); + log("\n"); + log(" -keep-outputs\n"); + log(" Do not remove module outputs. Taint tracking outputs will appear in the module ports\n"); + log(" alongside the orignal outputs.\n"); + log(" (default: original module outputs are removed)\n"); + log("\n"); + log(" -simple-cost-model\n"); + log(" Do not model logic area. Instead model the number of non-zero assignments to $anyconsts.\n"); + log(" Taint tracking logic versions vary in their size, but all reduced-precision versions are\n"); + log(" significantly smaller than the fully-precise version. A non-zero $anyconst assignment means\n"); + log(" that reduced-precision taint tracking logic was chosen for some gate.\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: use a complex model and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log("\n"); + log(" -no-cost-model\n"); + log(" Do not model taint tracking logic area and do not create a `__glift_weight` wire.\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: model area and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log("\n"); + log(" -instrument-more\n"); + log(" Allow choice from more versions of (even simpler) taint tracking logic. A total\n"); + log(" of 8 versions of taint tracking logic will be added per gate, including the 4\n"); + log(" versions from `-create-instrumented-model` and these additional versions:\n"); + log("\n"); + log(" y_t = a_t\n"); + log(" y_t = b_t\n"); + log(" y_t = 1\n"); + log(" y_t = 0\n"); + log("\n"); + log(" Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: do not add more versions of taint tracking logic.\n"); + log("\n"); + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + bool opt_create_precise_model = false, opt_create_imprecise_model = false, opt_create_instrumented_model = false; + bool opt_taintconstants = false, opt_keepoutputs = false, opt_simplecostmodel = false, opt_nocostmodel = false; + bool opt_instrumentmore = false; + log_header(design, "Executing GLIFT pass (creating and manipulating GLIFT models).\n"); + std::vector<std::string>::size_type argidx; + + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-create-precise-model") { + opt_create_precise_model = true; + continue; + } + if (args[argidx] == "-create-imprecise-model") { + opt_create_imprecise_model = true; + continue; + } + if (args[argidx] == "-create-instrumented-model") { + opt_create_instrumented_model = true; + continue; + } + if (args[argidx] == "-taint-constants") { + opt_taintconstants = true; + continue; + } + if (args[argidx] == "-keep-outputs") { + opt_keepoutputs = true; + continue; + } + if (args[argidx] == "-simple-cost-model") { + opt_simplecostmodel = true; + continue; + } + if (args[argidx] == "-no-cost-model") { + opt_nocostmodel = true; + continue; + } + if (args[argidx] == "-instrument-more") { + opt_instrumentmore = true; + continue; + } + break; + } + if(!opt_create_precise_model && !opt_create_imprecise_model && !opt_create_instrumented_model) + log_cmd_error("No command provided. See help for usage.\n"); + if(static_cast<int>(opt_create_precise_model) + static_cast<int>(opt_create_imprecise_model) + static_cast<int>(opt_create_instrumented_model) != 1) + log_cmd_error("Only one command may be specified. See help for usage.\n"); + if(opt_simplecostmodel && opt_nocostmodel) + log_cmd_error("Only one of `-simple-cost-model` and `-no-cost-model` may be specified. See help for usage.\n"); + if((opt_simplecostmodel || opt_nocostmodel) && !opt_create_instrumented_model) + log_cmd_error("Options `-simple-cost-model` and `-no-cost-model` may only be used with `-create-instrumented-model`. See help for usage.\n"); + extra_args(args, argidx, design); + + if (GetSize(design->selected_modules()) == 0) + log_cmd_error("Can't operate on an empty selection!\n"); + + TopoSort<RTLIL::Module*, IdString::compare_ptr_by_name<RTLIL::Module>> topo_modules; //cribbed from passes/techmap/flatten.cc + auto worklist = design->selected_modules(); + pool<RTLIL::IdString> non_top_modules; + while (!worklist.empty()) { + RTLIL::Module *module = *(worklist.begin()); + worklist.erase(worklist.begin()); + topo_modules.node(module); + + for (auto cell : module->selected_cells()) { + RTLIL::Module *tpl = design->module(cell->type); + if (tpl != nullptr) { + if (topo_modules.database.count(tpl) == 0) + worklist.push_back(tpl); + topo_modules.edge(tpl, module); + non_top_modules.insert(cell->type); + } + } + } + + if (!topo_modules.sort()) + log_cmd_error("Cannot handle recursive module instantiations.\n"); + + for (auto i = 0; i < GetSize(topo_modules.sorted); ++i) { + RTLIL::Module *module = topo_modules.sorted[i]; + GliftWorker(module, !non_top_modules[module->name], opt_create_precise_model, opt_create_imprecise_model, opt_create_instrumented_model, opt_taintconstants, opt_keepoutputs, opt_simplecostmodel, opt_nocostmodel, opt_instrumentmore); + } + } +} GliftPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/select.cc b/passes/cmds/select.cc index bb7b78cfe..d609c8d0f 100644 --- a/passes/cmds/select.cc +++ b/passes/cmds/select.cc @@ -944,12 +944,14 @@ static void select_stmt(RTLIL::Design *design, std::string arg, bool disable_emp } } -static std::string describe_selection_for_assert(RTLIL::Design *design, RTLIL::Selection *sel) +static std::string describe_selection_for_assert(RTLIL::Design *design, RTLIL::Selection *sel, bool whole_modules = false) { std::string desc = "Selection contains:\n"; for (auto mod : design->modules()) { if (sel->selected_module(mod->name)) { + if (whole_modules && sel->selected_whole_module(mod->name)) + desc += stringf("%s\n", id2cstr(mod->name)); for (auto wire : mod->wires()) if (sel->selected_member(mod->name, wire->name)) desc += stringf("%s/%s\n", id2cstr(mod->name), id2cstr(wire->name)); @@ -1051,17 +1053,17 @@ struct SelectPass : public Pass { log("\n"); log(" -unset <name>\n"); log(" do not modify the current selection. instead remove a previously saved\n"); - log(" selection under the given name (see @<name> below)."); + log(" selection under the given name (see @<name> below).\n"); log("\n"); log(" -assert-none\n"); log(" do not modify the current selection. instead assert that the given\n"); - log(" selection is empty. i.e. produce an error if any object matching the\n"); - log(" selection is found.\n"); + log(" selection is empty. i.e. produce an error if any object or module\n"); + log(" matching the selection is found.\n"); log("\n"); log(" -assert-any\n"); log(" do not modify the current selection. instead assert that the given\n"); - log(" selection is non-empty. i.e. produce an error if no object matching\n"); - log(" the selection is found.\n"); + log(" selection is non-empty. i.e. produce an error if no object or module\n"); + log(" matching the selection is found.\n"); log("\n"); log(" -assert-count N\n"); log(" do not modify the current selection. instead assert that the given\n"); @@ -1488,7 +1490,7 @@ struct SelectPass : public Pass { { RTLIL::Selection *sel = &work_stack.back(); sel->optimize(design); - std::string desc = describe_selection_for_assert(design, sel); + std::string desc = describe_selection_for_assert(design, sel, true); log_error("Assertion failed: selection is not empty:%s\n%s", sel_str.c_str(), desc.c_str()); } return; @@ -1503,7 +1505,7 @@ struct SelectPass : public Pass { { RTLIL::Selection *sel = &work_stack.back(); sel->optimize(design); - std::string desc = describe_selection_for_assert(design, sel); + std::string desc = describe_selection_for_assert(design, sel, true); log_error("Assertion failed: selection is empty:%s\n%s", sel_str.c_str(), desc.c_str()); } return; diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index 750fe0e10..35aa410e4 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -52,7 +52,7 @@ struct ShowWorker std::map<RTLIL::IdString, int> autonames; int single_idx_count; - struct net_conn { std::set<std::string> in, out; int bits; std::string color; }; + struct net_conn { std::set<std::pair<std::string, int>> in, out; std::string color; }; std::map<std::string, net_conn> net_conn_map; FILE *f; @@ -239,6 +239,19 @@ struct ShowWorker int idx = single_idx_count++; for (int rep, i = int(sig.chunks().size())-1; i >= 0; i -= rep) { const RTLIL::SigChunk &c = sig.chunks().at(i); + int cl, cr; + if (c.wire) { + if (c.wire->upto) { + cr = c.wire->start_offset + (c.wire->width - c.offset - 1); + cl = cr - (c.width - 1); + } else { + cr = c.wire->start_offset + c.offset; + cl = cr + c.width - 1; + } + } else { + cl = c.offset + c.width - 1; + cr = c.offset; + } if (!driver && c.wire == nullptr) { RTLIL::State s1 = c.data.front(); for (auto s2 : c.data) @@ -254,9 +267,8 @@ struct ShowWorker std::string repinfo = rep > 1 ? stringf("%dx ", rep) : ""; if (driver) { log_assert(!net.empty()); - label_string += stringf("<s%d> %d:%d - %s%d:%d |", i, pos, pos-c.width+1, repinfo.c_str(), c.offset+c.width-1, c.offset); - net_conn_map[net].in.insert(stringf("x%d:s%d", idx, i)); - net_conn_map[net].bits = rep*c.width; + label_string += stringf("<s%d> %d:%d - %s%d:%d |", i, pos, pos-c.width+1, repinfo.c_str(), cl, cr); + net_conn_map[net].in.insert({stringf("x%d:s%d", idx, i), rep*c.width}); net_conn_map[net].color = nextColor(c, net_conn_map[net].color); } else if (net.empty()) { @@ -268,9 +280,8 @@ struct ShowWorker c.data.front() == State::Sz ? 'Z' : '?', pos, pos-rep*c.width+1); } else { - label_string += stringf("<s%d> %s%d:%d - %d:%d |", i, repinfo.c_str(), c.offset+c.width-1, c.offset, pos, pos-rep*c.width+1); - net_conn_map[net].out.insert(stringf("x%d:s%d", idx, i)); - net_conn_map[net].bits = rep*c.width; + label_string += stringf("<s%d> %s%d:%d - %d:%d |", i, repinfo.c_str(), cl, cr, pos, pos-rep*c.width+1); + net_conn_map[net].out.insert({stringf("x%d:s%d", idx, i), rep*c.width}); net_conn_map[net].color = nextColor(c, net_conn_map[net].color); } pos -= rep * c.width; @@ -280,6 +291,7 @@ struct ShowWorker code += stringf("x%d [ shape=record, style=rounded, label=\"%s\" ];\n", idx, label_string.c_str()); if (!port.empty()) { currentColor = xorshift32(currentColor); + log_warning("WIDTHLABEL %s %d\n", log_signal(sig), GetSize(sig)); if (driver) code += stringf("%s:e -> x%d:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", port.c_str(), idx, nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); else @@ -292,10 +304,9 @@ struct ShowWorker { if (!port.empty()) { if (driver) - net_conn_map[net].in.insert(port); + net_conn_map[net].in.insert({port, GetSize(sig)}); else - net_conn_map[net].out.insert(port); - net_conn_map[net].bits = sig.size(); + net_conn_map[net].out.insert({port, GetSize(sig)}); net_conn_map[net].color = nextColor(sig, net_conn_map[net].color); } if (node != nullptr) @@ -465,8 +476,7 @@ struct ShowWorker std::string code, node; code += gen_portbox("", sig, false, &node); fprintf(f, "%s", code.c_str()); - net_conn_map[node].out.insert(stringf("p%d", pidx)); - net_conn_map[node].bits = sig.size(); + net_conn_map[node].out.insert({stringf("p%d", pidx), GetSize(sig)}); net_conn_map[node].color = nextColor(sig, net_conn_map[node].color); } @@ -474,8 +484,7 @@ struct ShowWorker std::string code, node; code += gen_portbox("", sig, true, &node); fprintf(f, "%s", code.c_str()); - net_conn_map[node].in.insert(stringf("p%d", pidx)); - net_conn_map[node].bits = sig.size(); + net_conn_map[node].in.insert({stringf("p%d", pidx), GetSize(sig)}); net_conn_map[node].color = nextColor(sig, net_conn_map[node].color); } @@ -509,17 +518,15 @@ struct ShowWorker currentColor = xorshift32(currentColor); fprintf(f, "%s:e -> %s:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", left_node.c_str(), right_node.c_str(), nextColor(conn).c_str(), widthLabel(conn.first.size()).c_str()); } else { - net_conn_map[right_node].bits = conn.first.size(); net_conn_map[right_node].color = nextColor(conn, net_conn_map[right_node].color); - net_conn_map[left_node].bits = conn.first.size(); net_conn_map[left_node].color = nextColor(conn, net_conn_map[left_node].color); if (left_node[0] == 'x') { - net_conn_map[right_node].in.insert(left_node); + net_conn_map[right_node].in.insert({left_node, GetSize(conn.first)}); } else if (right_node[0] == 'x') { - net_conn_map[left_node].out.insert(right_node); + net_conn_map[left_node].out.insert({right_node, GetSize(conn.first)}); } else { - net_conn_map[right_node].in.insert(stringf("x%d:e", single_idx_count)); - net_conn_map[left_node].out.insert(stringf("x%d:w", single_idx_count)); + net_conn_map[right_node].in.insert({stringf("x%d:e", single_idx_count), GetSize(conn.first)}); + net_conn_map[left_node].out.insert({stringf("x%d:w", single_idx_count), GetSize(conn.first)}); fprintf(f, "x%d [shape=box, style=rounded, label=\"BUF\"];\n", single_idx_count++); } } @@ -529,12 +536,13 @@ struct ShowWorker { currentColor = xorshift32(currentColor); if (wires_on_demand.count(it.first) > 0) { - if (it.second.in.size() == 1 && it.second.out.size() > 1 && it.second.in.begin()->compare(0, 1, "p") == 0) + if (it.second.in.size() == 1 && it.second.out.size() > 1 && it.second.in.begin()->first.compare(0, 1, "p") == 0) it.second.out.erase(*it.second.in.begin()); if (it.second.in.size() == 1 && it.second.out.size() == 1) { - std::string from = *it.second.in.begin(), to = *it.second.out.begin(); + std::string from = it.second.in.begin()->first, to = it.second.out.begin()->first; + int bits = it.second.in.begin()->second; if (from != to || from.compare(0, 1, "p") != 0) - fprintf(f, "%s:e -> %s:w [%s, %s];\n", from.c_str(), to.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str()); + fprintf(f, "%s:e -> %s:w [%s, %s];\n", from.c_str(), to.c_str(), nextColor(it.second.color).c_str(), widthLabel(bits).c_str()); continue; } if (it.second.in.size() == 0 || it.second.out.size() == 0) @@ -543,9 +551,9 @@ struct ShowWorker fprintf(f, "%s [ shape=point ];\n", it.first.c_str()); } for (auto &it2 : it.second.in) - fprintf(f, "%s:e -> %s:w [%s, %s];\n", it2.c_str(), it.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str()); + fprintf(f, "%s:e -> %s:w [%s, %s];\n", it2.first.c_str(), it.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it2.second).c_str()); for (auto &it2 : it.second.out) - fprintf(f, "%s:e -> %s:w [%s, %s];\n", it.first.c_str(), it2.c_str(), nextColor(it.second.color).c_str(), widthLabel(it.second.bits).c_str()); + fprintf(f, "%s:e -> %s:w [%s, %s];\n", it.first.c_str(), it2.first.c_str(), nextColor(it.second.color).c_str(), widthLabel(it2.second).c_str()); } fprintf(f, "}\n"); @@ -653,7 +661,7 @@ struct ShowPass : public Pass { log(" (including inout ports) are on the right side.\n"); log("\n"); log(" -pause\n"); - log(" wait for the use to press enter to before returning\n"); + log(" wait for the user to press enter to before returning\n"); log("\n"); log(" -enum\n"); log(" enumerate objects with internal ($-prefixed) names\n"); diff --git a/passes/cmds/sta.cc b/passes/cmds/sta.cc new file mode 100644 index 000000000..13e1ee13c --- /dev/null +++ b/passes/cmds/sta.cc @@ -0,0 +1,312 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at> + * (C) 2019 Eddie Hung <eddie@fpgeh.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/timinginfo.h" +#include <deque> + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct StaWorker +{ + Design *design; + Module *module; + SigMap sigmap; + + struct t_data { + Cell* driver; + IdString dst_port, src_port; + vector<tuple<SigBit,int,IdString>> fanouts; + SigBit backtrack; + t_data() : driver(nullptr) {} + }; + dict<SigBit, t_data> data; + std::deque<SigBit> queue; + struct t_endpoint { + Cell *sink; + IdString port; + int required; + t_endpoint() : sink(nullptr), required(0) {} + }; + dict<SigBit, t_endpoint> endpoints; + + int maxarrival; + SigBit maxbit; + + pool<SigBit> driven; + + StaWorker(RTLIL::Module *module) : design(module->design), module(module), sigmap(module), maxarrival(0) + { + TimingInfo timing; + + for (auto cell : module->cells()) + { + Module *inst_module = design->module(cell->type); + if (!inst_module) { + log_warning("Cell type '%s' not recognised! Ignoring.\n", log_id(cell->type)); + continue; + } + + if (!inst_module->get_blackbox_attribute()) { + log_warning("Cell type '%s' is not a black- nor white-box! Ignoring.\n", log_id(cell->type)); + continue; + } + + IdString derived_type = inst_module->derive(design, cell->parameters); + inst_module = design->module(derived_type); + log_assert(inst_module); + + if (!timing.count(derived_type)) { + auto &t = timing.setup_module(inst_module); + if (t.has_inputs && t.comb.empty() && t.arrival.empty() && t.required.empty()) + log_warning("Module '%s' has no timing arcs!\n", log_id(cell->type)); + } + + auto &t = timing.at(derived_type); + if (t.comb.empty() && t.arrival.empty() && t.required.empty()) + continue; + + pool<std::pair<SigBit,TimingInfo::NameBit>> src_bits, dst_bits; + + for (auto &conn : cell->connections()) { + auto rhs = sigmap(conn.second); + for (auto i = 0; i < GetSize(rhs); i++) { + const auto &bit = rhs[i]; + if (!bit.wire) + continue; + TimingInfo::NameBit namebit(conn.first,i); + if (cell->input(conn.first)) { + src_bits.insert(std::make_pair(bit,namebit)); + + auto it = t.required.find(namebit); + if (it == t.required.end()) + continue; + auto r = endpoints.insert(bit); + if (r.second || r.first->second.required < it->second.first) { + r.first->second.sink = cell; + r.first->second.port = conn.first; + r.first->second.required = it->second.first; + } + } + if (cell->output(conn.first)) { + dst_bits.insert(std::make_pair(bit,namebit)); + auto &d = data[bit]; + d.driver = cell; + d.dst_port = conn.first; + driven.insert(bit); + + auto it = t.arrival.find(namebit); + if (it == t.arrival.end()) + continue; + const auto &s = it->second.second; + if (cell->hasPort(s.name)) { + auto s_bit = sigmap(cell->getPort(s.name)[s.offset]); + if (s_bit.wire) + data[s_bit].fanouts.emplace_back(bit,it->second.first,s.name); + } + } + } + } + + for (const auto &s : src_bits) + for (const auto &d : dst_bits) { + auto it = t.comb.find(TimingInfo::BitBit(s.second,d.second)); + if (it == t.comb.end()) + continue; + data[s.first].fanouts.emplace_back(d.first,it->second,s.second.name); + } + } + + for (auto port_name : module->ports) { + auto wire = module->wire(port_name); + if (wire->port_input) { + for (const auto &b : sigmap(wire)) { + queue.emplace_back(b); + driven.insert(b); + } + // All primary inputs to arrive at time zero + wire->set_intvec_attribute(ID::sta_arrival, std::vector<int>(GetSize(wire), 0)); + } + if (wire->port_output) + for (const auto &b : sigmap(wire)) + if (b.wire) + endpoints.insert(b); + } + } + + void run() + { + while (!queue.empty()) { + auto b = queue.front(); + queue.pop_front(); + auto it = data.find(b); + if (it == data.end()) + continue; + const auto& src_arrivals = b.wire->get_intvec_attribute(ID::sta_arrival); + log_assert(GetSize(src_arrivals) == GetSize(b.wire)); + auto src_arrival = src_arrivals[b.offset]; + for (const auto &d : it->second.fanouts) { + const auto &dst_bit = std::get<0>(d); + auto dst_arrivals = dst_bit.wire->get_intvec_attribute(ID::sta_arrival); + if (dst_arrivals.empty()) + dst_arrivals = std::vector<int>(GetSize(dst_bit.wire), -1); + else + log_assert(GetSize(dst_arrivals) == GetSize(dst_bit.wire)); + auto &dst_arrival = dst_arrivals[dst_bit.offset]; + auto new_arrival = src_arrival + std::get<1>(d); + if (dst_arrival < new_arrival) { + auto dst_wire = dst_bit.wire; + dst_arrival = std::max(dst_arrival, new_arrival); + dst_wire->set_intvec_attribute(ID::sta_arrival, dst_arrivals); + queue.emplace_back(dst_bit); + + data[dst_bit].backtrack = b; + data[dst_bit].src_port = std::get<2>(d); + + auto it = endpoints.find(dst_bit); + if (it != endpoints.end()) + new_arrival += it->second.required; + if (new_arrival > maxarrival && driven.count(b)) { + maxarrival = new_arrival; + maxbit = dst_bit; + } + } + } + } + + auto b = maxbit; + if (b == SigBit()) { + log("No timing paths found.\n"); + return; + } + + log("Latest arrival time in '%s' is %d:\n", log_id(module), maxarrival); + auto it = endpoints.find(maxbit); + if (it != endpoints.end() && it->second.sink) + log(" %6d %s (%s.%s)\n", maxarrival, log_id(it->second.sink), log_id(it->second.sink->type), log_id(it->second.port)); + else { + log(" %6d (%s)\n", maxarrival, b.wire->port_output ? "<primary output>" : "<unknown>"); + if (!b.wire->port_output) + log_warning("Critical-path does not terminate in a recognised endpoint.\n"); + } + auto jt = data.find(b); + while (jt != data.end()) { + int arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset]; + if (jt->second.driver) { + log(" %s\n", log_signal(b)); + log(" %6d %s (%s.%s->%s)\n", arrival, log_id(jt->second.driver), log_id(jt->second.driver->type), log_id(jt->second.src_port), log_id(jt->second.dst_port)); + } + else if (b.wire->port_input) + log(" %6d %s (%s)\n", arrival, log_signal(b), "<primary input>"); + else + log_abort(); + b = jt->second.backtrack; + jt = data.find(b); + } + + std::map<int, unsigned> arrival_histogram; + for (const auto &i : endpoints) { + const auto &b = i.first; + if (!driven.count(b)) + continue; + + if (!b.wire->attributes.count(ID::sta_arrival)) { + log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b)); + continue; + } + + auto arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset]; + if (arrival < 0) { + log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b)); + continue; + } + arrival += i.second.required; + arrival_histogram[arrival]++; + } + // Adapted from https://github.com/YosysHQ/nextpnr/blob/affb12cc27ebf409eade062c4c59bb98569d8147/common/timing.cc#L946-L969 + if (arrival_histogram.size() > 0) { + unsigned num_bins = 20; + unsigned bar_width = 60; + auto min_arrival = arrival_histogram.begin()->first; + auto max_arrival = arrival_histogram.rbegin()->first; + auto bin_size = std::max<unsigned>(1, ceil((max_arrival - min_arrival + 1) / float(num_bins))); + std::vector<unsigned> bins(num_bins); + unsigned max_freq = 0; + for (const auto &i : arrival_histogram) { + auto &bin = bins[(i.first - min_arrival) / bin_size]; + bin += i.second; + max_freq = std::max(max_freq, bin); + } + bar_width = std::min(bar_width, max_freq); + + log("\n"); + log("Arrival histogram:\n"); + log(" legend: * represents %d endpoint(s)\n", max_freq / bar_width); + log(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width); + for (int i = num_bins-1; i >= 0; --i) + log("(%6d, %6d] |%s%c\n", min_arrival + bin_size * (i + 1), min_arrival + bin_size * i, + std::string(bins[i] * bar_width / max_freq, '*').c_str(), + (bins[i] * bar_width) % max_freq > 0 ? '+' : ' '); + } + } +}; + +struct StaPass : public Pass { + StaPass() : Pass("sta", "perform static timing analysis") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sta [options] [selection]\n"); + log("\n"); + log("This command performs static timing analysis on the design. (Only considers\n"); + log("paths within a single module, so the design must be flattened.)\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing STA pass (static timing analysis).\n"); + + /* + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-TODO") { + continue; + } + break; + } + */ + + extra_args(args, 1, design); + + for (Module *module : design->selected_modules()) + { + if (module->has_processes_warn()) + continue; + + StaWorker worker(module); + worker.run(); + } + } +} StaPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 422810526..fffdda48e 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -117,10 +117,14 @@ struct statdata_t } else if (cell_type.in(ID($mux), ID($pmux))) cell_type = stringf("%s_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y))); + else if (cell_type == ID($bmux)) + cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Y)), GetSize(cell->getPort(ID::S))); + else if (cell_type == ID($demux)) + cell_type = stringf("%s_%d_%d", cell_type.c_str(), GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::S))); else if (cell_type.in( ID($sr), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($sdff), ID($sdffe), ID($sdffce), - ID($dlatch), ID($adlatch), ID($dlatchsr))) + ID($aldff), ID($aldffe), ID($dlatch), ID($adlatch), ID($dlatchsr))) cell_type = stringf("%s_%d", cell_type.c_str(), GetSize(cell->getPort(ID::Q))); } diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index 650036580..d40d6e59f 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -554,10 +554,14 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check // If any interface instances or interface ports were found in the module, we need to rederive it completely: if ((if_expander.interfaces_in_module.size() > 0 || has_interface_ports) && !module->get_bool_attribute(ID::interfaces_replaced_in_module)) { - module->reprocess_module(design, if_expander.interfaces_in_module); + module->expand_interfaces(design, if_expander.interfaces_in_module); return did_something; } + // Now that modules have been derived, we may want to reprocess this + // module given the additional available context. + if (module->reprocess_if_necessary(design)) + return true; for (auto &it : array_cells) { @@ -972,6 +976,19 @@ struct HierarchyPass : public Pass { if (mod->get_bool_attribute(ID::top)) top_mod = mod; + if (top_mod == nullptr && auto_top_mode) { + log_header(design, "Finding top of design hierarchy..\n"); + dict<Module*, int> db; + for (Module *mod : design->selected_modules()) { + int score = find_top_mod_score(design, mod, db); + log("root of %3d design levels: %-20s\n", score, log_id(mod)); + if (!top_mod || score > db[top_mod]) + top_mod = mod; + } + if (top_mod != nullptr) + log("Automatically selected %s as design top module.\n", log_id(top_mod)); + } + if (top_mod != nullptr && top_mod->name.begins_with("$abstract")) { IdString top_name = top_mod->name.substr(strlen("$abstract")); @@ -996,19 +1013,6 @@ struct HierarchyPass : public Pass { } } - if (top_mod == nullptr && auto_top_mode) { - log_header(design, "Finding top of design hierarchy..\n"); - dict<Module*, int> db; - for (Module *mod : design->selected_modules()) { - int score = find_top_mod_score(design, mod, db); - log("root of %3d design levels: %-20s\n", score, log_id(mod)); - if (!top_mod || score > db[top_mod]) - top_mod = mod; - } - if (top_mod != nullptr) - log("Automatically selected %s as design top module.\n", log_id(top_mod)); - } - if (flag_simcheck && top_mod == nullptr) log_error("Design has no top module.\n"); diff --git a/passes/memory/Makefile.inc b/passes/memory/Makefile.inc index 5a2c4ecfc..d9dca52df 100644 --- a/passes/memory/Makefile.inc +++ b/passes/memory/Makefile.inc @@ -9,4 +9,7 @@ OBJS += passes/memory/memory_map.o OBJS += passes/memory/memory_memx.o OBJS += passes/memory/memory_nordff.o OBJS += passes/memory/memory_narrow.o +OBJS += passes/memory/memory_libmap.o +OBJS += passes/memory/memory_bmux2rom.o +OBJS += passes/memory/memlib.o diff --git a/passes/memory/memlib.cc b/passes/memory/memlib.cc new file mode 100644 index 000000000..8a7adc9ac --- /dev/null +++ b/passes/memory/memlib.cc @@ -0,0 +1,1101 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2021 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "memlib.h" + +#include <ctype.h> + +USING_YOSYS_NAMESPACE + +using namespace MemLibrary; + +PRIVATE_NAMESPACE_BEGIN + +typedef dict<std::string, Const> Options; + +struct ClockDef { + ClkPolKind kind; + std::string name; +}; + +struct RawWrTransDef { + WrTransTargetKind target_kind; + std::string target_group; + WrTransKind kind; +}; + +struct PortWidthDef { + bool tied; + std::vector<int> wr_widths; + std::vector<int> rd_widths; +}; + +struct SrstDef { + ResetValKind val; + SrstKind kind; + bool block_wr; +}; + +struct Empty {}; + +template<typename T> struct Capability { + T val; + Options opts, portopts; + + Capability(T val, Options opts, Options portopts) : val(val), opts(opts), portopts(portopts) {} +}; + +template<typename T> using Caps = std::vector<Capability<T>>; + +struct PortGroupDef { + PortKind kind; + dict<std::string, pool<Const>> portopts; + std::vector<std::string> names; + Caps<Empty> forbid; + Caps<ClockDef> clock; + Caps<Empty> clken; + Caps<Empty> wrbe_separate; + Caps<PortWidthDef> width; + Caps<Empty> rden; + Caps<RdWrKind> rdwr; + Caps<ResetValKind> rdinit; + Caps<ResetValKind> rdarst; + Caps<SrstDef> rdsrst; + Caps<std::string> wrprio; + Caps<RawWrTransDef> wrtrans; + Caps<Empty> optional; + Caps<Empty> optional_rw; +}; + +struct WidthsDef { + std::vector<int> widths; + WidthMode mode; +}; + +struct ResourceDef { + std::string name; + int count; +}; + +struct RamDef { + IdString id; + dict<std::string, pool<Const>> opts; + RamKind kind; + Caps<Empty> forbid; + Caps<Empty> prune_rom; + Caps<PortGroupDef> ports; + Caps<int> abits; + Caps<WidthsDef> widths; + Caps<ResourceDef> resource; + Caps<double> cost; + Caps<double> widthscale; + Caps<int> byte; + Caps<MemoryInitKind> init; + Caps<std::string> style; +}; + +struct Parser { + std::string filename; + std::ifstream infile; + int line_number = 0; + Library &lib; + const pool<std::string> &defines; + pool<std::string> &defines_unused; + std::vector<std::string> tokens; + int token_idx = 0; + bool eof = false; + + std::vector<std::pair<std::string, Const>> option_stack; + std::vector<std::pair<std::string, Const>> portoption_stack; + RamDef ram; + PortGroupDef port; + bool active = true; + + Parser(std::string filename, Library &lib, const pool<std::string> &defines, pool<std::string> &defines_unused) : filename(filename), lib(lib), defines(defines), defines_unused(defines_unused) { + // Note: this rewrites the filename we're opening, but not + // the one we're storing — this is actually correct, so that + // we keep the original filename for diagnostics. + rewrite_filename(filename); + infile.open(filename); + if (infile.fail()) { + log_error("failed to open %s\n", filename.c_str()); + } + parse(); + infile.close(); + } + + std::string peek_token() { + if (eof) + return ""; + + if (token_idx < GetSize(tokens)) + return tokens[token_idx]; + + tokens.clear(); + token_idx = 0; + + std::string line; + while (std::getline(infile, line)) { + line_number++; + for (string tok = next_token(line); !tok.empty(); tok = next_token(line)) { + if (tok[0] == '#') + break; + if (tok[tok.size()-1] == ';') { + tokens.push_back(tok.substr(0, tok.size()-1)); + tokens.push_back(";"); + } else { + tokens.push_back(tok); + } + } + if (!tokens.empty()) + return tokens[token_idx]; + } + + eof = true; + return ""; + } + + std::string get_token() { + std::string res = peek_token(); + if (!eof) + token_idx++; + return res; + } + + void eat_token(std::string expected) { + std::string token = get_token(); + if (token != expected) { + log_error("%s:%d: expected `%s`, got `%s`.\n", filename.c_str(), line_number, expected.c_str(), token.c_str()); + } + } + + IdString get_id() { + std::string token = get_token(); + if (token.empty() || (token[0] != '$' && token[0] != '\\')) { + log_error("%s:%d: expected id string, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + return IdString(token); + } + + std::string get_name() { + std::string res = get_token(); + bool valid = true; + // Basic sanity check. + if (res.empty() || (!isalpha(res[0]) && res[0] != '_')) + valid = false; + for (char c: res) + if (!isalnum(c) && c != '_') + valid = false; + if (!valid) + log_error("%s:%d: expected name, got `%s`.\n", filename.c_str(), line_number, res.c_str()); + return res; + } + + std::string get_string() { + std::string token = get_token(); + if (token.size() < 2 || token[0] != '"' || token[token.size()-1] != '"') { + log_error("%s:%d: expected string, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + return token.substr(1, token.size()-2); + } + + bool peek_string() { + std::string token = peek_token(); + return !token.empty() && token[0] == '"'; + } + + int get_int() { + std::string token = get_token(); + char *endptr; + long res = strtol(token.c_str(), &endptr, 0); + if (token.empty() || *endptr || res > INT_MAX) { + log_error("%s:%d: expected int, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + return res; + } + + double get_double() { + std::string token = get_token(); + char *endptr; + double res = strtod(token.c_str(), &endptr); + if (token.empty() || *endptr) { + log_error("%s:%d: expected float, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + return res; + } + + bool peek_int() { + std::string token = peek_token(); + return !token.empty() && isdigit(token[0]); + } + + void get_semi() { + std::string token = get_token(); + if (token != ";") { + log_error("%s:%d: expected `;`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + } + + Const get_value() { + std::string token = peek_token(); + if (!token.empty() && token[0] == '"') { + std::string s = get_string(); + return Const(s); + } else { + return Const(get_int()); + } + } + + bool enter_ifdef(bool polarity) { + bool res = active; + std::string name = get_name(); + defines_unused.erase(name); + if (active) { + if (defines.count(name)) { + active = polarity; + } else { + active = !polarity; + } + } + return res; + } + + void enter_else(bool save) { + get_token(); + active = !active && save; + } + + void enter_option() { + std::string name = get_string(); + Const val = get_value(); + if (active) { + ram.opts[name].insert(val); + } + option_stack.push_back({name, val}); + } + + void exit_option() { + option_stack.pop_back(); + } + + Options get_options() { + Options res; + for (auto it: option_stack) + res[it.first] = it.second; + return res; + } + + void enter_portoption() { + std::string name = get_string(); + Const val = get_value(); + if (active) { + port.portopts[name].insert(val); + } + portoption_stack.push_back({name, val}); + } + + void exit_portoption() { + portoption_stack.pop_back(); + } + + Options get_portoptions() { + Options res; + for (auto it: portoption_stack) + res[it.first] = it.second; + return res; + } + + template<typename T> void add_cap(Caps<T> &caps, T val) { + if (active) + caps.push_back(Capability<T>(val, get_options(), get_portoptions())); + } + + void parse_port_block() { + if (peek_token() == "{") { + get_token(); + while (peek_token() != "}") + parse_port_item(); + get_token(); + } else { + parse_port_item(); + } + } + + void parse_ram_block() { + if (peek_token() == "{") { + get_token(); + while (peek_token() != "}") + parse_ram_item(); + get_token(); + } else { + parse_ram_item(); + } + } + + void parse_top_block() { + if (peek_token() == "{") { + get_token(); + while (peek_token() != "}") + parse_top_item(); + get_token(); + } else { + parse_top_item(); + } + } + + void parse_port_item() { + std::string token = get_token(); + if (token == "ifdef") { + bool save = enter_ifdef(true); + parse_port_block(); + if (peek_token() == "else") { + enter_else(save); + parse_port_block(); + } + active = save; + } else if (token == "ifndef") { + bool save = enter_ifdef(false); + parse_port_block(); + if (peek_token() == "else") { + enter_else(save); + parse_port_block(); + } + active = save; + } else if (token == "option") { + enter_option(); + parse_port_block(); + exit_option(); + } else if (token == "portoption") { + enter_portoption(); + parse_port_block(); + exit_portoption(); + } else if (token == "clock") { + if (port.kind == PortKind::Ar) { + log_error("%s:%d: `clock` not allowed in async read port.\n", filename.c_str(), line_number); + } + ClockDef def; + token = get_token(); + if (token == "anyedge") { + def.kind = ClkPolKind::Anyedge; + } else if (token == "posedge") { + def.kind = ClkPolKind::Posedge; + } else if (token == "negedge") { + def.kind = ClkPolKind::Negedge; + } else { + log_error("%s:%d: expected `posedge`, `negedge`, or `anyedge`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + if (peek_string()) { + def.name = get_string(); + } + get_semi(); + add_cap(port.clock, def); + } else if (token == "clken") { + if (port.kind == PortKind::Ar) { + log_error("%s:%d: `clken` not allowed in async read port.\n", filename.c_str(), line_number); + } + get_semi(); + add_cap(port.clken, {}); + } else if (token == "wrbe_separate") { + if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) { + log_error("%s:%d: `wrbe_separate` not allowed in read port.\n", filename.c_str(), line_number); + } + get_semi(); + add_cap(port.wrbe_separate, {}); + } else if (token == "width") { + PortWidthDef def; + token = peek_token(); + bool is_rw = port.kind == PortKind::Srsw || port.kind == PortKind::Arsw; + if (token == "tied") { + get_token(); + if (!is_rw) + log_error("%s:%d: `tied` only makes sense for read+write ports.\n", filename.c_str(), line_number); + while (peek_int()) + def.wr_widths.push_back(get_int()); + def.tied = true; + } else if (token == "mix") { + get_token(); + if (!is_rw) + log_error("%s:%d: `mix` only makes sense for read+write ports.\n", filename.c_str(), line_number); + while (peek_int()) + def.wr_widths.push_back(get_int()); + def.rd_widths = def.wr_widths; + def.tied = false; + } else if (token == "rd") { + get_token(); + if (!is_rw) + log_error("%s:%d: `rd` only makes sense for read+write ports.\n", filename.c_str(), line_number); + do { + def.rd_widths.push_back(get_int()); + } while (peek_int()); + eat_token("wr"); + do { + def.wr_widths.push_back(get_int()); + } while (peek_int()); + def.tied = false; + } else if (token == "wr") { + get_token(); + if (!is_rw) + log_error("%s:%d: `wr` only makes sense for read+write ports.\n", filename.c_str(), line_number); + do { + def.wr_widths.push_back(get_int()); + } while (peek_int()); + eat_token("rd"); + do { + def.rd_widths.push_back(get_int()); + } while (peek_int()); + def.tied = false; + } else { + do { + def.wr_widths.push_back(get_int()); + } while (peek_int()); + def.tied = true; + } + get_semi(); + add_cap(port.width, def); + } else if (token == "rden") { + if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw) + log_error("%s:%d: `rden` only allowed on sync read ports.\n", filename.c_str(), line_number); + get_semi(); + add_cap(port.rden, {}); + } else if (token == "rdwr") { + if (port.kind != PortKind::Srsw) + log_error("%s:%d: `rdwr` only allowed on sync read+write ports.\n", filename.c_str(), line_number); + RdWrKind kind; + token = get_token(); + if (token == "undefined") { + kind = RdWrKind::Undefined; + } else if (token == "no_change") { + kind = RdWrKind::NoChange; + } else if (token == "new") { + kind = RdWrKind::New; + } else if (token == "old") { + kind = RdWrKind::Old; + } else if (token == "new_only") { + kind = RdWrKind::NewOnly; + } else { + log_error("%s:%d: expected `undefined`, `new`, `old`, `new_only`, or `no_change`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(port.rdwr, kind); + } else if (token == "rdinit") { + if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw) + log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str()); + ResetValKind kind; + token = get_token(); + if (token == "none") { + kind = ResetValKind::None; + } else if (token == "zero") { + kind = ResetValKind::Zero; + } else if (token == "any") { + kind = ResetValKind::Any; + } else if (token == "no_undef") { + kind = ResetValKind::NoUndef; + } else { + log_error("%s:%d: expected `none`, `zero`, `any`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(port.rdinit, kind); + } else if (token == "rdarst") { + if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw) + log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str()); + ResetValKind kind; + token = get_token(); + if (token == "none") { + kind = ResetValKind::None; + } else if (token == "zero") { + kind = ResetValKind::Zero; + } else if (token == "any") { + kind = ResetValKind::Any; + } else if (token == "no_undef") { + kind = ResetValKind::NoUndef; + } else if (token == "init") { + kind = ResetValKind::Init; + } else { + log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(port.rdarst, kind); + } else if (token == "rdsrst") { + if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw) + log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str()); + SrstDef def; + token = get_token(); + if (token == "none") { + def.val = ResetValKind::None; + } else if (token == "zero") { + def.val = ResetValKind::Zero; + } else if (token == "any") { + def.val = ResetValKind::Any; + } else if (token == "no_undef") { + def.val = ResetValKind::NoUndef; + } else if (token == "init") { + def.val = ResetValKind::Init; + } else { + log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + if (def.val == ResetValKind::None) { + def.kind = SrstKind::None; + } else { + token = get_token(); + if (token == "ungated") { + def.kind = SrstKind::Ungated; + } else if (token == "gated_clken") { + def.kind = SrstKind::GatedClkEn; + } else if (token == "gated_rden") { + def.kind = SrstKind::GatedRdEn; + } else { + log_error("%s:%d: expected `ungated`, `gated_clken` or `gated_rden`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + } + def.block_wr = false; + if (peek_token() == "block_wr") { + get_token(); + def.block_wr = true; + } + get_semi(); + add_cap(port.rdsrst, def); + } else if (token == "wrprio") { + if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) + log_error("%s:%d: `wrprio` only allowed on write ports.\n", filename.c_str(), line_number); + do { + add_cap(port.wrprio, get_string()); + } while (peek_string()); + get_semi(); + } else if (token == "wrtrans") { + if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) + log_error("%s:%d: `wrtrans` only allowed on write ports.\n", filename.c_str(), line_number); + token = peek_token(); + RawWrTransDef def; + if (token == "all") { + def.target_kind = WrTransTargetKind::All; + get_token(); + } else { + def.target_kind = WrTransTargetKind::Group; + def.target_group = get_string(); + } + token = get_token(); + if (token == "new") { + def.kind = WrTransKind::New; + } else if (token == "old") { + def.kind = WrTransKind::Old; + } else { + log_error("%s:%d: expected `new` or `old`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(port.wrtrans, def); + } else if (token == "forbid") { + get_semi(); + add_cap(port.forbid, {}); + } else if (token == "optional") { + get_semi(); + add_cap(port.optional, {}); + } else if (token == "optional_rw") { + get_semi(); + add_cap(port.optional_rw, {}); + } else if (token == "") { + log_error("%s:%d: unexpected EOF while parsing port item.\n", filename.c_str(), line_number); + } else { + log_error("%s:%d: unknown port-level item `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + } + + void parse_ram_item() { + std::string token = get_token(); + if (token == "ifdef") { + bool save = enter_ifdef(true); + parse_ram_block(); + if (peek_token() == "else") { + enter_else(save); + parse_ram_block(); + } + active = save; + } else if (token == "ifndef") { + bool save = enter_ifdef(false); + parse_ram_block(); + if (peek_token() == "else") { + enter_else(save); + parse_ram_block(); + } + active = save; + } else if (token == "option") { + enter_option(); + parse_ram_block(); + exit_option(); + } else if (token == "prune_rom") { + get_semi(); + add_cap(ram.prune_rom, {}); + } else if (token == "forbid") { + get_semi(); + add_cap(ram.forbid, {}); + } else if (token == "abits") { + int val = get_int(); + if (val < 0) + log_error("%s:%d: abits %d nagative.\n", filename.c_str(), line_number, val); + get_semi(); + add_cap(ram.abits, val); + } else if (token == "width") { + WidthsDef def; + int w = get_int(); + if (w <= 0) + log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w); + def.widths.push_back(w); + def.mode = WidthMode::Single; + get_semi(); + add_cap(ram.widths, def); + } else if (token == "widths") { + WidthsDef def; + int last = 0; + do { + int w = get_int(); + if (w <= 0) + log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w); + if (w < last * 2) + log_error("%s:%d: width %d smaller than %d required for progression.\n", filename.c_str(), line_number, w, last * 2); + last = w; + def.widths.push_back(w); + } while(peek_int()); + token = get_token(); + if (token == "global") { + def.mode = WidthMode::Global; + } else if (token == "per_port") { + def.mode = WidthMode::PerPort; + } else { + log_error("%s:%d: expected `global`, or `per_port`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(ram.widths, def); + } else if (token == "resource") { + ResourceDef def; + def.name = get_string(); + if (peek_int()) + def.count = get_int(); + else + def.count = 1; + get_semi(); + add_cap(ram.resource, def); + } else if (token == "cost") { + add_cap(ram.cost, get_double()); + get_semi(); + } else if (token == "widthscale") { + if (peek_int()) { + add_cap(ram.widthscale, get_double()); + } else { + add_cap(ram.widthscale, 0.0); + } + get_semi(); + } else if (token == "byte") { + int val = get_int(); + if (val <= 0) + log_error("%s:%d: byte width %d not positive.\n", filename.c_str(), line_number, val); + add_cap(ram.byte, val); + get_semi(); + } else if (token == "init") { + MemoryInitKind kind; + token = get_token(); + if (token == "zero") { + kind = MemoryInitKind::Zero; + } else if (token == "any") { + kind = MemoryInitKind::Any; + } else if (token == "no_undef") { + kind = MemoryInitKind::NoUndef; + } else if (token == "none") { + kind = MemoryInitKind::None; + } else { + log_error("%s:%d: expected `zero`, `any`, `none`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + get_semi(); + add_cap(ram.init, kind); + } else if (token == "style") { + do { + std::string val = get_string(); + for (auto &c: val) + c = std::tolower(c); + add_cap(ram.style, val); + } while (peek_string()); + get_semi(); + } else if (token == "port") { + port = PortGroupDef(); + token = get_token(); + if (token == "ar") { + port.kind = PortKind::Ar; + } else if (token == "sr") { + port.kind = PortKind::Sr; + } else if (token == "sw") { + port.kind = PortKind::Sw; + } else if (token == "arsw") { + port.kind = PortKind::Arsw; + } else if (token == "srsw") { + port.kind = PortKind::Srsw; + } else { + log_error("%s:%d: expected `ar`, `sr`, `sw`, `arsw`, or `srsw`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + do { + port.names.push_back(get_string()); + } while (peek_string()); + parse_port_block(); + if (active) + add_cap(ram.ports, port); + } else if (token == "") { + log_error("%s:%d: unexpected EOF while parsing ram item.\n", filename.c_str(), line_number); + } else { + log_error("%s:%d: unknown ram-level item `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + } + + void parse_top_item() { + std::string token = get_token(); + if (token == "ifdef") { + bool save = enter_ifdef(true); + parse_top_block(); + if (peek_token() == "else") { + enter_else(save); + parse_top_block(); + } + active = save; + } else if (token == "ifndef") { + bool save = enter_ifdef(false); + parse_top_block(); + if (peek_token() == "else") { + enter_else(save); + parse_top_block(); + } + active = save; + } else if (token == "ram") { + int orig_line = line_number; + ram = RamDef(); + token = get_token(); + if (token == "distributed") { + ram.kind = RamKind::Distributed; + } else if (token == "block") { + ram.kind = RamKind::Block; + } else if (token == "huge") { + ram.kind = RamKind::Huge; + } else { + log_error("%s:%d: expected `distributed`, `block`, or `huge`, got `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + ram.id = get_id(); + parse_ram_block(); + if (active) { + compile_ram(orig_line); + } + } else if (token == "") { + log_error("%s:%d: unexpected EOF while parsing top item.\n", filename.c_str(), line_number); + } else { + log_error("%s:%d: unknown top-level item `%s`.\n", filename.c_str(), line_number, token.c_str()); + } + } + + bool opts_ok(const Options &def, const Options &var) { + for (auto &it: def) + if (var.at(it.first) != it.second) + return false; + return true; + } + + template<typename T> const T *find_single_cap(const Caps<T> &caps, const Options &opts, const Options &portopts, const char *name) { + const T *res = nullptr; + for (auto &cap: caps) { + if (!opts_ok(cap.opts, opts)) + continue; + if (!opts_ok(cap.portopts, portopts)) + continue; + if (res) + log_error("%s:%d: duplicate %s cap.\n", filename.c_str(), line_number, name); + res = &cap.val; + } + return res; + } + + std::vector<Options> make_opt_combinations(const dict<std::string, pool<Const>> &opts) { + std::vector<Options> res; + res.push_back(Options()); + for (auto &it: opts) { + std::vector<Options> new_res; + for (auto &val: it.second) { + for (Options o: res) { + o[it.first] = val; + new_res.push_back(o); + } + } + res = new_res; + } + return res; + } + + void compile_portgroup(Ram &cram, PortGroupDef &pdef, dict<std::string, int> &clk_ids, const dict<std::string, int> &port_ids, int orig_line) { + PortGroup grp; + grp.optional = find_single_cap(pdef.optional, cram.options, Options(), "optional"); + grp.optional_rw = find_single_cap(pdef.optional_rw, cram.options, Options(), "optional_rw"); + grp.names = pdef.names; + for (auto portopts: make_opt_combinations(pdef.portopts)) { + bool forbidden = false; + for (auto &fdef: ram.forbid) { + if (opts_ok(fdef.opts, cram.options) && opts_ok(fdef.portopts, portopts)) { + forbidden = true; + } + } + if (forbidden) + continue; + PortVariant var; + var.options = portopts; + var.kind = pdef.kind; + if (pdef.kind != PortKind::Ar) { + const ClockDef *cdef = find_single_cap(pdef.clock, cram.options, portopts, "clock"); + if (!cdef) + log_error("%s:%d: missing clock capability.\n", filename.c_str(), orig_line); + var.clk_pol = cdef->kind; + if (cdef->name.empty()) { + var.clk_shared = -1; + } else { + auto it = clk_ids.find(cdef->name); + bool anyedge = cdef->kind == ClkPolKind::Anyedge; + if (it == clk_ids.end()) { + clk_ids[cdef->name] = var.clk_shared = GetSize(cram.shared_clocks); + RamClock clk; + clk.name = cdef->name; + clk.anyedge = anyedge; + cram.shared_clocks.push_back(clk); + } else { + var.clk_shared = it->second; + if (cram.shared_clocks[var.clk_shared].anyedge != anyedge) { + log_error("%s:%d: named clock \"%s\" used with both posedge/negedge and anyedge clocks.\n", filename.c_str(), orig_line, cdef->name.c_str()); + } + } + } + var.clk_en = find_single_cap(pdef.clken, cram.options, portopts, "clken"); + } + const PortWidthDef *wdef = find_single_cap(pdef.width, cram.options, portopts, "width"); + if (wdef) { + if (cram.width_mode != WidthMode::PerPort) + log_error("%s:%d: per-port width doesn't make sense for tied dbits.\n", filename.c_str(), orig_line); + compile_widths(var, cram.dbits, *wdef); + } else { + var.width_tied = true; + var.min_wr_wide_log2 = 0; + var.min_rd_wide_log2 = 0; + var.max_wr_wide_log2 = GetSize(cram.dbits) - 1; + var.max_rd_wide_log2 = GetSize(cram.dbits) - 1; + } + if (pdef.kind == PortKind::Srsw || pdef.kind == PortKind::Sr) { + const RdWrKind *rdwr = find_single_cap(pdef.rdwr, cram.options, portopts, "rdwr"); + var.rdwr = rdwr ? *rdwr : RdWrKind::Undefined; + var.rd_en = find_single_cap(pdef.rden, cram.options, portopts, "rden"); + const ResetValKind *iv = find_single_cap(pdef.rdinit, cram.options, portopts, "rdinit"); + var.rdinitval = iv ? *iv : ResetValKind::None; + const ResetValKind *arv = find_single_cap(pdef.rdarst, cram.options, portopts, "rdarst"); + var.rdarstval = arv ? *arv : ResetValKind::None; + const SrstDef *srv = find_single_cap(pdef.rdsrst, cram.options, portopts, "rdsrst"); + if (srv) { + var.rdsrstval = srv->val; + var.rdsrstmode = srv->kind; + var.rdsrst_block_wr = srv->block_wr; + if (srv->kind == SrstKind::GatedClkEn && !var.clk_en) + log_error("%s:%d: `gated_clken` used without `clken`.\n", filename.c_str(), orig_line); + if (srv->kind == SrstKind::GatedRdEn && !var.rd_en) + log_error("%s:%d: `gated_rden` used without `rden`.\n", filename.c_str(), orig_line); + } else { + var.rdsrstval = ResetValKind::None; + var.rdsrstmode = SrstKind::None; + var.rdsrst_block_wr = false; + } + if (var.rdarstval == ResetValKind::Init || var.rdsrstval == ResetValKind::Init) { + if (var.rdinitval != ResetValKind::Any && var.rdinitval != ResetValKind::NoUndef) { + log_error("%s:%d: reset value `init` has to be paired with `any` or `no_undef` initial value.\n", filename.c_str(), orig_line); + } + } + } + var.wrbe_separate = find_single_cap(pdef.wrbe_separate, cram.options, portopts, "wrbe_separate"); + if (var.wrbe_separate && cram.byte == 0) { + log_error("%s:%d: `wrbe_separate` used without `byte`.\n", filename.c_str(), orig_line); + } + for (auto &def: pdef.wrprio) { + if (!opts_ok(def.opts, cram.options)) + continue; + if (!opts_ok(def.portopts, portopts)) + continue; + var.wrprio.push_back(port_ids.at(def.val)); + } + for (auto &def: pdef.wrtrans) { + if (!opts_ok(def.opts, cram.options)) + continue; + if (!opts_ok(def.portopts, portopts)) + continue; + WrTransDef tdef; + tdef.target_kind = def.val.target_kind; + if (def.val.target_kind == WrTransTargetKind::Group) + tdef.target_group = port_ids.at(def.val.target_group); + tdef.kind = def.val.kind; + var.wrtrans.push_back(tdef); + } + grp.variants.push_back(var); + } + if (grp.variants.empty()) { + log_error("%s:%d: all port option combinations are forbidden.\n", filename.c_str(), orig_line); + } + cram.port_groups.push_back(grp); + } + + void compile_ram(int orig_line) { + if (ram.abits.empty()) + log_error("%s:%d: `dims` capability should be specified.\n", filename.c_str(), orig_line); + if (ram.widths.empty()) + log_error("%s:%d: `widths` capability should be specified.\n", filename.c_str(), orig_line); + if (ram.ports.empty()) + log_error("%s:%d: at least one port group should be specified.\n", filename.c_str(), orig_line); + for (auto opts: make_opt_combinations(ram.opts)) { + bool forbidden = false; + for (auto &fdef: ram.forbid) { + if (opts_ok(fdef.opts, opts)) { + forbidden = true; + } + } + if (forbidden) + continue; + Ram cram; + cram.id = ram.id; + cram.kind = ram.kind; + cram.options = opts; + cram.prune_rom = find_single_cap(ram.prune_rom, opts, Options(), "prune_rom"); + const int *abits = find_single_cap(ram.abits, opts, Options(), "abits"); + if (!abits) + continue; + cram.abits = *abits; + const WidthsDef *widths = find_single_cap(ram.widths, opts, Options(), "widths"); + if (!widths) + continue; + cram.dbits = widths->widths; + cram.width_mode = widths->mode; + const ResourceDef *resource = find_single_cap(ram.resource, opts, Options(), "resource"); + if (resource) { + cram.resource_name = resource->name; + cram.resource_count = resource->count; + } else { + cram.resource_count = 1; + } + cram.cost = 0; + for (auto &cap: ram.cost) { + if (opts_ok(cap.opts, opts)) + cram.cost += cap.val; + } + const double *widthscale = find_single_cap(ram.widthscale, opts, Options(), "widthscale"); + if (widthscale) + cram.widthscale = *widthscale ? *widthscale : cram.cost; + else + cram.widthscale = 0; + const int *byte = find_single_cap(ram.byte, opts, Options(), "byte"); + cram.byte = byte ? *byte : 0; + if (GetSize(cram.dbits) - 1 > cram.abits) + log_error("%s:%d: abits %d too small for dbits progression.\n", filename.c_str(), line_number, cram.abits); + validate_byte(widths->widths, cram.byte); + const MemoryInitKind *ik = find_single_cap(ram.init, opts, Options(), "init"); + cram.init = ik ? *ik : MemoryInitKind::None; + for (auto &sdef: ram.style) + if (opts_ok(sdef.opts, opts)) + cram.style.push_back(sdef.val); + dict<std::string, int> port_ids; + int ctr = 0; + for (auto &pdef: ram.ports) { + if (!opts_ok(pdef.opts, opts)) + continue; + for (auto &name: pdef.val.names) + port_ids[name] = ctr; + ctr++; + } + dict<std::string, int> clk_ids; + for (auto &pdef: ram.ports) { + if (!opts_ok(pdef.opts, opts)) + continue; + compile_portgroup(cram, pdef.val, clk_ids, port_ids, orig_line); + } + lib.rams.push_back(cram); + } + } + + void validate_byte(const std::vector<int> &widths, int byte) { + if (byte == 0) + return; + if (byte >= widths.back()) + return; + if (widths[0] % byte == 0) { + for (int j = 1; j < GetSize(widths); j++) + if (widths[j] % byte != 0) + log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte); + return; + } + for (int i = 0; i < GetSize(widths); i++) { + if (widths[i] == byte) { + for (int j = i + 1; j < GetSize(widths); j++) + if (widths[j] % byte != 0) + log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte); + return; + } + } + log_error("%s:%d: byte width %d invalid for dbits.\n", filename.c_str(), line_number, byte); + } + + void compile_widths(PortVariant &var, const std::vector<int> &widths, const PortWidthDef &width) { + var.width_tied = width.tied; + auto wr_widths = compile_widthdef(widths, width.wr_widths); + var.min_wr_wide_log2 = wr_widths.first; + var.max_wr_wide_log2 = wr_widths.second; + if (width.tied) { + var.min_rd_wide_log2 = wr_widths.first; + var.max_rd_wide_log2 = wr_widths.second; + } else { + auto rd_widths = compile_widthdef(widths, width.rd_widths); + var.min_rd_wide_log2 = rd_widths.first; + var.max_rd_wide_log2 = rd_widths.second; + } + } + + std::pair<int, int> compile_widthdef(const std::vector<int> &dbits, const std::vector<int> &widths) { + if (widths.empty()) + return {0, GetSize(dbits) - 1}; + for (int i = 0; i < GetSize(dbits); i++) { + if (dbits[i] == widths[0]) { + for (int j = 0; j < GetSize(widths); j++) { + if (i+j >= GetSize(dbits) || dbits[i+j] != widths[j]) { + log_error("%s:%d: port width %d doesn't match dbits progression.\n", filename.c_str(), line_number, widths[j]); + } + } + return {i, i + GetSize(widths) - 1}; + } + } + log_error("%s:%d: port width %d invalid for dbits.\n", filename.c_str(), line_number, widths[0]); + } + + void parse() { + while (peek_token() != "") + parse_top_item(); + } +}; + +PRIVATE_NAMESPACE_END + +Library MemLibrary::parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines) { + Library res; + pool<std::string> defines_unused = defines; + for (auto &file: filenames) { + Parser(file, res, defines, defines_unused); + } + for (auto def: defines_unused) { + log_warning("define %s not used in the library.\n", def.c_str()); + } + return res; +} diff --git a/passes/memory/memlib.h b/passes/memory/memlib.h new file mode 100644 index 000000000..c3f7728f1 --- /dev/null +++ b/passes/memory/memlib.h @@ -0,0 +1,171 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2021 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MEMLIB_H +#define MEMLIB_H + +#include <string> +#include <vector> + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace MemLibrary { + +enum class RamKind { + Auto, + Logic, + NotLogic, + Distributed, + Block, + Huge, +}; + +enum class WidthMode { + Single, + Global, + PerPort, +}; + +enum class MemoryInitKind { + None, + Zero, + Any, + NoUndef, +}; + +enum class PortKind { + Sr, + Ar, + Sw, + Srsw, + Arsw, +}; + +enum class ClkPolKind { + Anyedge, + Posedge, + Negedge, +}; + +enum class RdWrKind { + Undefined, + NoChange, + New, + Old, + NewOnly, +}; + +enum class ResetValKind { + None, + Zero, + Any, + NoUndef, + Init, +}; + +enum class SrstKind { + None, + Ungated, + GatedClkEn, + GatedRdEn, +}; + +enum class WrTransTargetKind { + All, + Group, +}; + +enum class WrTransKind { + New, + Old, +}; + +struct WrTransDef { + WrTransTargetKind target_kind; + int target_group; + WrTransKind kind; +}; + +struct PortVariant { + dict<std::string, Const> options; + PortKind kind; + int clk_shared; + ClkPolKind clk_pol; + bool clk_en; + bool width_tied; + int min_wr_wide_log2; + int max_wr_wide_log2; + int min_rd_wide_log2; + int max_rd_wide_log2; + bool rd_en; + RdWrKind rdwr; + ResetValKind rdinitval; + ResetValKind rdarstval; + ResetValKind rdsrstval; + SrstKind rdsrstmode; + bool rdsrst_block_wr; + bool wrbe_separate; + std::vector<int> wrprio; + std::vector<WrTransDef> wrtrans; +}; + +struct PortGroup { + bool optional; + bool optional_rw; + std::vector<std::string> names; + std::vector<PortVariant> variants; +}; + +struct RamClock { + std::string name; + bool anyedge; +}; + +struct Ram { + IdString id; + RamKind kind; + dict<std::string, Const> options; + std::vector<PortGroup> port_groups; + bool prune_rom; + int abits; + std::vector<int> dbits; + WidthMode width_mode; + std::string resource_name; + int resource_count; + double cost; + double widthscale; + int byte; + MemoryInitKind init; + std::vector<std::string> style; + std::vector<RamClock> shared_clocks; +}; + +struct Library { + std::vector<Ram> rams; +}; + +Library parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines); + +} + +YOSYS_NAMESPACE_END + +#endif diff --git a/passes/memory/memlib.md b/passes/memory/memlib.md new file mode 100644 index 000000000..fdc2d4bed --- /dev/null +++ b/passes/memory/memlib.md @@ -0,0 +1,505 @@ +# The `memory_libmap` pass + +The `memory_libmap` pass is used to map memories to hardware primitives. To work, +it needs a description of available target memories in a custom format. + + +## Basic structure + +A basic library could look like this: + + # A distributed-class RAM called $__RAM16X4SDP_ + ram distributed $__RAM16X4SDP_ { + # Has 4 address bits (ie. 16 rows). + abits 4; + # Has 4 data bits. + width 4; + # Cost for the selection heuristic. + cost 4; + # Can be initialized to any value on startup. + init any; + # Has a synchronous write port called "W"... + port sw "W" { + # ... with a positive edge clock. + clock posedge; + } + # Has an asynchronous read port called "R". + port ar "R" { + } + } + + # A block-class RAM called $__RAMB9K_ + ram block $__RAMB9K_ { + # Has 13 address bits in the base (most narrow) data width. + abits 13; + # The available widths are: + # - 1 (13 address bits) + # - 2 (12 address bits) + # - 4 (11 address bits) + # - 9 (10 address bits) + # - 18 (9 address bits) + # The width selection is per-port. + widths 1 2 4 9 18 per_port; + # Has a write enable signal with 1 bit for every 9 data bits. + byte 9; + cost 64; + init any; + # Has two synchronous read+write ports, called "A" and "B". + port srsw "A" "B" { + clock posedge; + # Has a clock enable signal (gates both read and write). + clken; + # Has three per-port selectable options for handling read+write behavior: + portoption "RDWR" "NO_CHANGE" { + # When port is writing, reading is not done (output register keeps + # its value). + rdwr no_change; + } + portoption "RDWR" "OLD" { + # When port is writing, the data read is the old value (before the + # write). + rdwr old; + } + portoption "RDWR" "NEW" { + # When port is writing, the data read is the new value. + rdwr new; + } + } + } + +The pass will automatically select between the two available cells and +the logic fallback (mapping the whole memory to LUTs+FFs) based on required +capabilities and cost function. The selected memories will be transformed +to intermediate `$__RAM16X4SDP_` and `$__RAMB9K_` cells that need to be mapped +to actual hardware cells by a `techmap` pass, while memories selected for logic +fallback will be left unmapped and will be later mopped up by `memory_map` pass. + +## RAM definition blocks + +The syntax for a RAM definition is: + + ram <kind: distributed|block|huge> <name> { + <ram properties> + <ports> + } + +The `<name>` is used as the type of the mapped cell that will be passed to `techmap`. +The memory kind is one of `distributed`, `block`, or `huge`. It describes the general +class of the memory and can be matched on by manual selection attributes. + +The available ram properties are: + +- `abits <address bits>;` +- `width <width>;` +- `widths <width 1> <width 2> ... <width n> <global|per_port>;` +- `byte <width>;` +- `cost <cost>;` +- `widthscale [<factor>];` +- `resource <name> <count>;` +- `init <none|zero|any|no_undef>;` +- `style "<name 1>" "<name 2>" "<name 3>" ...;` +- `prune_rom;` + +### RAM dimensions + +The memory dimensions are described by `abits` and `width` or `widths` properties. + +For a simple memory cell with a fixed width, use `abits` and `width` like this: + + abits 4; + width 4; + +This will result in a `2**abits × width` memory cell. + +Multiple-width memories are also possible, and use the `widths` property instead. +The rules for multiple-width memories are: + +- the widths are given in `widths` property in increasing order +- the value in the `abits` property corresponds to the most narrow width +- every width in the list needs to be greater than or equal to twice + the previous width (ie. `1 2 4 9 18` is valid, `1 2 4 7 14` is not) +- it is assumed that, for every width in progression, the word in memory + is made of two smaller words, plus optionally some extra bits (eg. in the above + list, the 9-bit word is made of two 4-bit words and 1 extra bit), and thus + each sequential width in the list corresponds to one fewer usable address bit +- all addresses connected to memory ports are always `abits` bits wide, with const + zero wired to the unused bits corresponding to wide ports + +When multiple widths are specified, they can be `per_port` or `global`. +For the `global` version, the pass has to pick one width for the whole cell, +and it is set on the resulting cell as the `WIDTH` parameter. For the `per_port` +version, the selection is made on per-port basis, and passed using `PORT_*_WIDTH` +parameters. When the mode is `per_port`, the width selection can be fine-tuned +with the port `width` property. + +Specifying dimensions is mandatory. + + +### Byte width + +If the memory cell has per-byte write enables, the `byte` property can be used +to define the byte size (ie. how many data bits correspond to one write enable +bit). + +The property is optional. If not used, it is assumed that there is a single +write enable signal for each writable port. + +The rules for this property are as follows: + +- for every available width, the width needs to be a multiple of the byte size, + or the byte size needs to be larger than the width +- if the byte size is larger than the width, the byte enable signel is assumed + to be one bit wide and cover the whole port +- otherwise, the byte enable signal has one bit for every `byte` bits of the + data port + +The exact kind of byte enable signal is determined by the presence or absence +of the per-port `wrbe_separate` property. + + +### Cost properties + +The `cost` property is used to estimate the cost of using a given mapping. +This is the cost of using one cell, and will be scaled as appropriate if +the mapping requires multiple cells. + +If the `widthscale` property is specified, the mapping is assumed to be flexible, +with cost scaling with the percentage of data width actually used. The value +of the `widthscale` property is how much of the cost is scalable as such. +If the value is omitted, all of the cost is assumed to scale. +Eg. for the following properties: + + width 14; + cost 8; + widthscale 7; + +The cost of a given cell will be assumed to be `(8 - 7) + 7 * (used_bits / 14)`. + +If `widthscale` is used, The pass will attach a `BITS_USED` parameter to mapped +calls, with a bitmask of which data bits of the memory are actually in use. +The parameter width will be the widest width in the `widths` property, and +the bit correspondence is defined accordingly. + +The `cost` property is mandatory. + + +### `init` property + +This property describes the state of the memory at initialization time. Can have +one of the following values: + +- `none`: the memory contents are unpredictable, memories requiring any sort + of initialization will not be mapped to this cell +- `zero`: the memory contents are zero, memories can be mapped to this cell iff + their initialization value is entirely zero or undef +- `any`: the memory contents can be arbitrarily selected, and the initialization + will be passes as the `INIT` parameter to the mapped cell +- `no_undef`: like `any`, but only 0 and 1 bit values are supported (the pass will + convert any x bits to 0) + +The `INIT` parameter is always constructed as a concatenation of words corresponding +to the widest available `widths` setting, so that all available memory cell bits +are covered. + +This property is optional and assumed to be `none` when not present. + + +### `style` property + +Provides a name (or names) for this definition that can be passed to the `ram_style` +or similar attribute to manually select it. Optional and can be used multiple times. + + +### `prune_rom` property + +Specifying this property disqualifies the definition from consideration for source +memories that have no write ports (ie. ROMs). Use this on definitions that have +an obviously superior read-only alternative (eg. LUTRAMs) to make the pass skip +them over quickly. + + +## Port definition blocks + +The syntax for a port group definition is: + + port <ar|sr|sw|arsw|srsw> "NAME 1" "NAME 2" ... { + <port properties> + } + +A port group definition defines a group of ports with identical properties. +There are as many ports in a group as there are names given. + +Ports come in 5 kinds: + +- `ar`: asynchronous read port +- `sr`: synchronous read port +- `sw`: synchronous write port +- `arsw`: simultanous synchronous write + asynchronous read with common address (commonly found in LUT RAMs) +- `srsw`: synchronous write + synchronous read with common address + +The port properties available are: + +- `width <tied|mix>;` +- `width <width 1> <width 2> ...;` +- `width <tied|mix> <width 1> <width 2> ...;` +- `width rd <width 1> <width 2> ... wr <width 1> <width 2> ...;` +- `clock <posedge|negedge|anyedge> ["SHARED_NAME"];` +- `clken;` +- `rden;` +- `wrbe_separate;` +- `rdwr <undefined|no_change|new|old|new_only>;` +- `rdinit <none|zero|any|no_undef>;` +- `rdarst <none|zero|any|no_undef|init>;` +- `rdsrst <none|zero|any|no_undef|init> <ungated|gatec_clken|gated_rden> [block_wr];` +- `wrprio "NAME" "NAME" ...;` +- `wrtrans <"NAME"|all> <old|new>;` +- `optional;` +- `optional_rw;` + +The base signals connected to the mapped cell for ports are: + +- `PORT_<name>_ADDR`: the address +- `PORT_<name>_WR_DATA`: the write data (for `sw`/`arsw`/`srsw` ports only) +- `PORT_<name>_RD_DATA`: the read data (for `ar`/`sr`/`arsw`/`srsw` ports only) +- `PORT_<name>_WR_EN`: the write enable or enables (for `sw`/`arsw`/`srsw` ports only) + +The address is always `abits` wide. If a non-narrowest width is used, the appropriate low +bits will be tied to 0. + + +### Port `width` prooperty + +If the RAM has `per_port` widths, the available width selection can be further described +on per-port basis, by using one of the following properties: + +- `width tied;`: any width from the master `widths` list is acceptable, and + (for read+write ports) the read and write width has to be the same +- `width tied <width 1> <width 2> ...;`: like above, but limits the width + selection to the given list; the list has to be a contiguous sublist of the + master `widths` list +- `width <width 1> <width 2> ...;`: alias for the above, to be used for read-only + or write-only ports +- `width mix;`: any width from the master `widths` list is acceptable, and + read width can be different than write width (only usable for read+write ports) +- `width mix <width 1> <width 2> ...;`: like above, but limits the width + selection to the given list; the list has to be a contiguous sublist of the + master `widths` list +- `width rd <width 1> <width 2> ... wr <width 1> <width 2> ...;`: like above, + but the limitted selection can be different for read and write widths + +If `per_port` widths are in use and this property is not specified, `width tied;` is assumed. + +The parameters attached to the cell in `per_port` widths mode are: + +- `PORT_<name>_WIDTH`: the selected width (for `tied` ports) +- `PORT_<name>_RD_WIDTH`: the selected read width (for `mix` ports) +- `PORT_<name>_WR_WIDTH`: the selected write width (for `mix` ports) + + +### `clock` property + +The `clock` property is used with synchronous ports (and synchronous ports only). +It is mandatory for them and describes the clock polarity and clock sharing. +`anyedge` means that both polarities are supported. + +If a shared clock name is provided, the port is assumed to have a shared clock signal +with all other ports using the same shared name. Otherwise, the port is assumed to +have its own clock signal. + +The port clock is always provided on the memory cell as `PORT_<name>_CLK` signal +(even if it is also shared). Shared clocks are also provided as `CLK_<shared_name>` +signals. + +For `anyedge` clocks, the cell gets a `PORT_<name>_CLKPOL` parameter that is set +to 1 for `posedge` clocks and 0 for `negedge` clocks. If the clock is shared, +the same information will also be provided as `CLK_<shared_name>_POL` parameter. + + +### `clken` and `rden` + +The `clken` property, if present, means that the port has a clock enable signal +gating both reads and writes. Such signal will be provided to the mapped cell +as `PORT_<name>_CLK_EN`. It is only applicable to synchronous ports. + +The `rden` property, if present, means that the port has a read clock enable signal. +Such signal will be provided to the mapped cell as `PORT_<name>_RD_EN`. It is only +applicable to synchronous read ports (`sr` and `srsw`). + +For `sr` ports, both of these options are effectively equivalent. + + +### `wrbe_separate` and the write enables + +The `wrbe_separate` property specifies that the write byte enables are provided +as a separate signal from the main write enable. It can only be used when the +RAM-level `byte` property is also specified. + +The rules are as follows: + +If no `byte` is specified: + +- `wrbe_separate` is not allowed +- `PORT_<name>_WR_EN` signal is single bit + +If `byte` is specified, but `wrbe_separate` is not: + +- `PORT_<name>_WR_EN` signal has one bit for every data byte +- `PORT_<name>_WR_EN_WIDTH` parameter is the width of the above (only present for multiple-width cells) + +If `byte` is specified and `wrbe_separate` is present: + +- `PORT_<name>_WR_EN` signal is single bit +- `PORT_<name>_WR_BE` signal has one bit for every data byte +- `PORT_<name>_WR_BE_WIDTH` parameter is the width of the above (only present for multiple-width cells) +- a given byte is written iff all of `CLK_EN` (if present), `WR_EN`, and the corresponding `WR_BE` bit are one + +This property can only be used on write ports. + + +### `rdwr` property + +This property is allowed only on `srsw` ports and describes read-write interactions. + +The possible values are: + +- `no_change`: if write is being performed (any bit of `WR_EN` is set), + reading is not performed and the `RD_DATA` keeps its old value +- `undefined`: all `RD_DATA` bits corresponding to enabled `WR_DATA` bits + have undefined value, remaining bits read from memory +- `old`: all `RD_DATA` bits get the previous value in memory +- `new`: all `RD_DATA` bits get the new value in memory (transparent write) +- `new_only`: all `RD_DATA` bits corresponding to enabled `WR_DATA` bits + get the new value, all others are undefined + +If this property is not found on an `srsw` port, `undefined` is assumed. + + +### Read data initial value and resets + +The `rdinit`, `rdarst`, and `rdsrst` are applicable only to synchronous read +ports. + +`rdinit` describes the initial value of the read port data, and can be set to +one of the following: + +- `none`: initial data is indeterminate +- `zero`: initial data is all-0 +- `any`: initial data is arbitrarily configurable, and the selected value + will be attached to the cell as `PORT_<name>_RD_INIT_VALUE` parameter +- `no_undef`: like `any`, but only 0 and 1 bits are allowed + +`rdarst` and `rdsrst` describe the asynchronous and synchronous reset capabilities. +The values are similar to `rdinit`: + +- `none`: no reset +- `zero`: reset to all-0 data +- `any`: reset to arbitrary value, the selected value + will be attached to the cell as `PORT_<name>_RD_ARST_VALUE` or + `PORT_<name>_RD_SRST_VALUE` parameter +- `no_undef`: like `any`, but only 0 and 1 bits are allowed +- `init`: reset to the initial value, as specified by `rdinit` (which must be `any` + or `no_undef` itself) + +If the capability is anything other than `none`, the reset signal +will be provided as `PORT_<name>_RD_ARST` or `PORT_<name>_RD_SRST`. + +For `rdsrst`, the priority must be additionally specified, as one of: + +- `ungated`: `RD_SRST` has priority over both `CLK_EN` and `RD_EN` (if present) +- `gated_clken`: `CLK_EN` has priority over `RD_SRST`; `RD_SRST` has priority over `RD_EN` if present +- `gated_rden`: `RD_EN` and `CLK_EN` (if present) both have priority over `RD_SRST` + +Also, `rdsrst` can optionally have `block_wr` specified, which means that sync reset +cannot be performed in the same cycle as a write. + +If not provided, `none` is assumed for all three properties. + + +### Write priority + +The `wrprio` property is only allowed on write ports and defines a priority relationship +between port — when `wrprio "B";` is used in definition of port `"A"`, and both ports +simultanously write to the same memory cell, the value written by port `"A"` will have +precedence. + +This property is optional, and can be used multiple times as necessary. If no relationship +is described for a pair of write ports, no priority will be assumed. + + +### Write transparency + +The `wrtrans` property is only allowed on write ports and defines behavior when +another synchronous read port reads from the memory cell at the same time as the +given port writes it. The values are: + +- `old`: the read port will get the old value of the cell +- `new`: the read port will get the new value of the cell + +This property is optional, and can be used multiple times as necessary. If no relationship +is described for a pair of ports, the value read is assumed to be indeterminate. + +Note that this property is not used to describe the read value on the port itself for `srsw` +ports — for that purpose, the `rdwr` property is used instead. + + +### Optional ports + +The `optional;` property will make the pass attach a `PORT_<name>_USED` parameter +with a boolean value specifying whether a given port was meaningfully used in +mapping a given cell. Likewise, `optional_rw;` will attach `PORT_<name>_RD_USED` +and `PORT_<name>_WR_USED` the specify whether the read / write part in particular +was used. These can be useful if the mapping has some meaningful optimization +to apply for unused ports, but doesn't otherwise influence the selection process. + + +## Options + +For highly configurable cells, multiple variants may be described in one cell description. +All properties and port definitions within a RAM or port definition can be put inside +an `option` block as follows: + + option "NAME" <value> { + <properties, ports, ...> + } + +The value and name of an option are arbitrary, and the selected option value +will be provided to the cell as `OPTION_<name>` parameter. Values can be +strings or integers. + + +Likewise, for per-port options, a `portoption` block can be used: + + portoption "NAME" <value> { + <properties, ...> + } + +These options will be provided as `PORT_<pname>_OPTION_<oname>` parameters. + +The library parser will simply expand the RAM definition for every possible combination +of option values mentioned in the RAM body, and likewise for port definitions. +This can lead to a combinatorial explosion. + +If some option values cannot be used together, a `forbid` pseudo-property can be used +to discard a given combination, eg: + + option "ABC" 1 { + portoption "DEF" "GHI" { + forbid; + } + } + +will disallow combining the RAM option `ABC = 2` with port option `DEF = "GHI"`. + + +## Ifdefs + +To allow reusing a library for multiple FPGA families with slighly differing +capabilities, `ifdef` (and `ifndef`) blocks are provided: + + ifdef IS_FANCY_FPGA_WITH_CONFIGURABLE_ASYNC_RESET { + rdarst any; + } else { + rdarst zero; + } + +Such blocks can be enabled by passing the `-D` option to the pass. diff --git a/passes/memory/memory.cc b/passes/memory/memory.cc index bac547c1a..e34cc927e 100644 --- a/passes/memory/memory.cc +++ b/passes/memory/memory.cc @@ -31,13 +31,14 @@ struct MemoryPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" memory [-nomap] [-nordff] [-nowiden] [-nosat] [-memx] [-bram <bram_rules>] [selection]\n"); + log(" memory [-norom] [-nomap] [-nordff] [-nowiden] [-nosat] [-memx] [-bram <bram_rules>] [selection]\n"); log("\n"); log("This pass calls all the other memory_* passes in a useful order:\n"); log("\n"); log(" opt_mem\n"); log(" opt_mem_priority\n"); log(" opt_mem_feedback\n"); + log(" memory_bmux2rom (skipped if called with -norom)\n"); log(" memory_dff (skipped if called with -nordff or -memx)\n"); log(" opt_clean\n"); log(" memory_share [-nowiden] [-nosat]\n"); @@ -54,6 +55,7 @@ struct MemoryPass : public Pass { } void execute(std::vector<std::string> args, RTLIL::Design *design) override { + bool flag_norom = false; bool flag_nomap = false; bool flag_nordff = false; bool flag_memx = false; @@ -65,6 +67,10 @@ struct MemoryPass : public Pass { size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-norom") { + flag_norom = true; + continue; + } if (args[argidx] == "-nomap") { flag_nomap = true; continue; @@ -97,6 +103,8 @@ struct MemoryPass : public Pass { Pass::call(design, "opt_mem"); Pass::call(design, "opt_mem_priority"); Pass::call(design, "opt_mem_feedback"); + if (!flag_norom) + Pass::call(design, "memory_bmux2rom"); if (!flag_nordff) Pass::call(design, "memory_dff"); Pass::call(design, "opt_clean"); diff --git a/passes/memory/memory_bmux2rom.cc b/passes/memory/memory_bmux2rom.cc new file mode 100644 index 000000000..a3fc5a7fc --- /dev/null +++ b/passes/memory/memory_bmux2rom.cc @@ -0,0 +1,87 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/mem.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct MemoryBmux2RomPass : public Pass { + MemoryBmux2RomPass() : Pass("memory_bmux2rom", "convert muxes to ROMs") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" memory_bmux2rom [options] [selection]\n"); + log("\n"); + log("This pass converts $bmux cells with constant A input to ROMs.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing MEMORY_BMUX2ROM pass (converting muxes to ROMs).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + for (auto cell : module->selected_cells()) { + if (cell->type != ID($bmux)) + continue; + + SigSpec sig_a = cell->getPort(ID::A); + if (!sig_a.is_fully_const()) + continue; + + int abits = cell->getParam(ID::S_WIDTH).as_int(); + int width = cell->getParam(ID::WIDTH).as_int(); + if (abits < 3) + continue; + + // Ok, let's do it. + Mem mem(module, NEW_ID, width, 0, 1 << abits); + mem.attributes = cell->attributes; + + MemInit init; + init.addr = 0; + init.data = sig_a.as_const(); + init.en = Const(State::S1, width); + mem.inits.push_back(std::move(init)); + + MemRd rd; + rd.addr = cell->getPort(ID::S); + rd.data = cell->getPort(ID::Y); + rd.init_value = Const(State::Sx, width); + rd.arst_value = Const(State::Sx, width); + rd.srst_value = Const(State::Sx, width); + mem.rd_ports.push_back(std::move(rd)); + + mem.emit(); + module->remove(cell); + } + } + } +} MemoryBmux2RomPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/memory/memory_bram.cc b/passes/memory/memory_bram.cc index fed9d60c0..b1f45d5fc 100644 --- a/passes/memory/memory_bram.cc +++ b/passes/memory/memory_bram.cc @@ -644,22 +644,6 @@ grow_read_ports:; log(" Bram port %c%d.%d has incompatible clock polarity.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); goto skip_bram_rport; } - if (port.en != State::S1 && pi.enable == 0) { - log(" Bram port %c%d.%d has no read enable input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); - goto skip_bram_rport; - } - if (port.arst != State::S0) { - log(" Bram port %c%d.%d has no async reset input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); - goto skip_bram_rport; - } - if (port.srst != State::S0) { - log(" Bram port %c%d.%d has no sync reset input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); - goto skip_bram_rport; - } - if (!port.init_value.is_fully_undef()) { - log(" Bram port %c%d.%d has no initial value support.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); - goto skip_bram_rport; - } if (non_transp && read_transp.count(pi.transp) && read_transp.at(pi.transp)) { log(" Bram port %c%d.%d has incompatible read transparency.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1); goto skip_bram_rport; @@ -794,10 +778,18 @@ grow_read_ports:; // Apply make_outreg and make_transp where necessary. for (auto &pi : portinfos) { - if (pi.make_outreg) + if (pi.mapped_port == -1 || pi.wrmode) + continue; + auto &port = mem.rd_ports[pi.mapped_port]; + if (pi.make_outreg) { mem.extract_rdff(pi.mapped_port, initvals); + } else if (port.clk_enable) { + if (!pi.enable && port.en != State::S1) + mem.emulate_rden(pi.mapped_port, initvals); + else + mem.emulate_reset(pi.mapped_port, true, true, true, initvals); + } if (pi.make_transp) { - auto &port = mem.rd_ports[pi.mapped_port]; for (int i = 0; i < GetSize(mem.wr_ports); i++) if (port.transparency_mask[i]) mem.emulate_transparency(i, pi.mapped_port, initvals); diff --git a/passes/memory/memory_dff.cc b/passes/memory/memory_dff.cc index 21962c238..91209d428 100644 --- a/passes/memory/memory_dff.cc +++ b/passes/memory/memory_dff.cc @@ -71,9 +71,9 @@ struct MemQueryCache // port_ren is an upper bound on when we care about the value fetched // from memory this cycle. int ren = ezSAT::CONST_TRUE; - if (ff.has_en) { - ren = qcsat.importSigBit(ff.sig_en); - if (!ff.pol_en) + if (ff.has_ce) { + ren = qcsat.importSigBit(ff.sig_ce); + if (!ff.pol_ce) ren = qcsat.ez->NOT(ren); } if (ff.has_srst) { @@ -347,6 +347,10 @@ struct MemoryDffWorker log("output latches are not supported.\n"); return; } + if (ff.has_aload) { + log("output FF has async load, not supported.\n"); + return; + } if (ff.has_sr) { // Latches and FFs with SR are not supported. log("output FF has both set and reset, not supported.\n"); @@ -491,8 +495,8 @@ struct MemoryDffWorker log("merging output FF to cell.\n"); merger.remove_output_ff(bits); - if (ff.has_en && !ff.pol_en) - ff.sig_en = module->LogicNot(NEW_ID, ff.sig_en); + if (ff.has_ce && !ff.pol_ce) + ff.sig_ce = module->LogicNot(NEW_ID, ff.sig_ce); if (ff.has_arst && !ff.pol_arst) ff.sig_arst = module->LogicNot(NEW_ID, ff.sig_arst); if (ff.has_srst && !ff.pol_srst) @@ -500,8 +504,8 @@ struct MemoryDffWorker port.clk = ff.sig_clk; port.clk_enable = true; port.clk_polarity = ff.pol_clk; - if (ff.has_en) - port.en = ff.sig_en; + if (ff.has_ce) + port.en = ff.sig_ce; else port.en = State::S1; if (ff.has_arst) { @@ -551,6 +555,10 @@ struct MemoryDffWorker log("address latches are not supported.\n"); return; } + if (ff.has_aload) { + log("address FF has async load, not supported.\n"); + return; + } if (ff.has_sr || ff.has_arst) { log("address FF has async set and/or reset, not supported.\n"); return; @@ -573,7 +581,7 @@ struct MemoryDffWorker // Now we're commited to merge it. merger.mark_input_ff(bits); // If the address FF has enable and/or sync reset, unmap it. - ff.unmap_ce_srst(module); + ff.unmap_ce_srst(); port.clk = ff.sig_clk; port.en = State::S1; port.addr = ff.sig_d; diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc new file mode 100644 index 000000000..ab7bb7bb2 --- /dev/null +++ b/passes/memory/memory_libmap.cc @@ -0,0 +1,2093 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2021 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "memlib.h" + +#include <ctype.h> + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/mem.h" +#include "kernel/qcsat.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using namespace MemLibrary; + +#define FACTOR_MUX 0.5 +#define FACTOR_DEMUX 0.5 +#define FACTOR_EMU 2 + +struct PassOptions { + bool no_auto_distributed; + bool no_auto_block; + bool no_auto_huge; + double logic_cost_rom; + double logic_cost_ram; +}; + +struct WrPortConfig { + // Index of the read port this port is merged with, or -1 if none. + int rd_port; + // Index of the PortGroup in the Ram. + int port_group; + int port_variant; + const PortVariant *def; + // Emulate priority logic for this list of (source) write port indices. + std::vector<int> emu_prio; + // If true, this port needs to end up with uniform byte enables to work correctly. + bool force_uniform; + + WrPortConfig() : rd_port(-1), force_uniform(false) {} +}; + +struct RdPortConfig { + // Index of the write port this port is merged with, or -1 if none. + int wr_port; + // Index of the PortGroup in the Ram. + int port_group; + int port_variant; + const PortVariant *def; + // If true, this is a sync port mapped into async mem, make an output + // register. Mutually exclusive with the following options. + bool emu_sync; + // Emulate the EN / ARST / SRST / init value circuitry. + bool emu_en; + bool emu_arst; + bool emu_srst; + bool emu_init; + // Emulate EN-SRST priority. + bool emu_srst_en_prio; + // If true, use clk_en as rd_en. + bool rd_en_to_clk_en; + // Emulate transparency logic for this list of (source) write port indices. + std::vector<int> emu_trans; + + RdPortConfig() : wr_port(-1), emu_sync(false), emu_en(false), emu_arst(false), emu_srst(false), emu_init(false), emu_srst_en_prio(false), rd_en_to_clk_en(false) {} +}; + +// The named clock and clock polarity assignments. +struct SharedClockConfig { + bool used; + SigBit clk; + // For anyedge clocks. + bool polarity; + // For non-anyedge clocks. + bool invert; +}; + +struct MemConfig { + // Reference to the library ram definition + const Ram *def; + // Port assignments, indexed by Mem port index. + std::vector<WrPortConfig> wr_ports; + std::vector<RdPortConfig> rd_ports; + std::vector<SharedClockConfig> shared_clocks; + // Emulate read-first write-read behavior using soft logic. + bool emu_read_first; + // This many low bits of (target) address are always-0 on all ports. + int base_width_log2; + int unit_width_log2; + std::vector<int> swizzle; + int hard_wide_mask; + int emu_wide_mask; + // How many times the base memory block will need to be duplicated to get more + // data bits. + int repl_d; + // How many times the whole memory array will need to be duplicated to cover + // all read ports required. + int repl_port; + // Emulation score — how much circuitry we need to add for priority / transparency / + // reset / initial value emulation. + int score_emu; + // Mux score — how much circuitry we need to add to manually decode whatever address + // bits are not decoded by the memory array itself, for reads. + int score_mux; + // Demux score — how much circuitry we need to add to manually decode whatever address + // bits are not decoded by the memory array itself, for writes. + int score_demux; + double cost; + MemConfig() : emu_read_first(false) {} +}; + +typedef std::vector<MemConfig> MemConfigs; + +struct MapWorker { + Module *module; + ModWalker modwalker; + SigMap sigmap; + SigMap sigmap_xmux; + FfInitVals initvals; + + MapWorker(Module *module) : module(module), modwalker(module->design, module), sigmap(module), sigmap_xmux(module), initvals(&sigmap, module) { + for (auto cell : module->cells()) + { + if (cell->type == ID($mux)) + { + RTLIL::SigSpec sig_a = sigmap_xmux(cell->getPort(ID::A)); + RTLIL::SigSpec sig_b = sigmap_xmux(cell->getPort(ID::B)); + + if (sig_a.is_fully_undef()) + sigmap_xmux.add(cell->getPort(ID::Y), sig_b); + else if (sig_b.is_fully_undef()) + sigmap_xmux.add(cell->getPort(ID::Y), sig_a); + } + } + } +}; + +struct SwizzleBit { + bool valid; + int mux_idx; + int addr; + int bit; +}; + +struct Swizzle { + int addr_shift; + int addr_start; + int addr_end; + std::vector<int> addr_mux_bits; + std::vector<std::vector<SwizzleBit>> bits; +}; + +struct MemMapping { + MapWorker &worker; + QuickConeSat qcsat; + Mem &mem; + const Library &lib; + const PassOptions &opts; + std::vector<MemConfig> cfgs; + bool logic_ok; + double logic_cost; + RamKind kind; + std::string style; + dict<int, int> wr_en_cache; + dict<std::pair<int, int>, bool> wr_implies_rd_cache; + dict<std::pair<int, int>, bool> wr_excludes_rd_cache; + dict<std::pair<int, int>, bool> wr_excludes_srst_cache; + + MemMapping(MapWorker &worker, Mem &mem, const Library &lib, const PassOptions &opts) : worker(worker), qcsat(worker.modwalker), mem(mem), lib(lib), opts(opts) { + determine_style(); + logic_ok = determine_logic_ok(); + if (GetSize(mem.wr_ports) == 0) + logic_cost = mem.width * mem.size * opts.logic_cost_rom; + else + logic_cost = mem.width * mem.size * opts.logic_cost_ram; + if (kind == RamKind::Logic) + return; + for (int i = 0; i < GetSize(lib.rams); i++) { + auto &rdef = lib.rams[i]; + if (!check_ram_kind(rdef)) + continue; + if (!check_ram_style(rdef)) + continue; + if (!check_init(rdef)) + continue; + if (rdef.prune_rom && mem.wr_ports.empty()) + continue; + MemConfig cfg; + cfg.def = &rdef; + for (auto &cdef: rdef.shared_clocks) { + (void)cdef; + SharedClockConfig clk; + clk.used = false; + cfg.shared_clocks.push_back(clk); + } + cfgs.push_back(cfg); + } + assign_wr_ports(); + assign_rd_ports(); + handle_trans(); + // If we got this far, the memory is mappable. The following two can require emulating + // some functionality, but cannot cause the mapping to fail. + handle_priority(); + handle_rd_rst(); + score_emu_ports(); + // Now it is just a matter of picking geometry. + handle_geom(); + dump_configs(0); + prune_post_geom(); + dump_configs(1); + } + + bool addr_compatible(int wpidx, int rpidx) { + auto &wport = mem.wr_ports[wpidx]; + auto &rport = mem.rd_ports[rpidx]; + int max_wide_log2 = std::max(rport.wide_log2, wport.wide_log2); + SigSpec raddr = rport.addr.extract_end(max_wide_log2); + SigSpec waddr = wport.addr.extract_end(max_wide_log2); + int abits = std::max(GetSize(raddr), GetSize(waddr)); + raddr.extend_u0(abits); + waddr.extend_u0(abits); + return worker.sigmap_xmux(raddr) == worker.sigmap_xmux(waddr); + } + + int get_wr_en(int wpidx) { + auto it = wr_en_cache.find(wpidx); + if (it != wr_en_cache.end()) + return it->second; + int res = qcsat.ez->expression(qcsat.ez->OpOr, qcsat.importSig(mem.wr_ports[wpidx].en)); + wr_en_cache.insert({wpidx, res}); + return res; + } + + bool get_wr_implies_rd(int wpidx, int rpidx) { + auto key = std::make_pair(wpidx, rpidx); + auto it = wr_implies_rd_cache.find(key); + if (it != wr_implies_rd_cache.end()) + return it->second; + int wr_en = get_wr_en(wpidx); + int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]); + qcsat.prepare(); + bool res = !qcsat.ez->solve(wr_en, qcsat.ez->NOT(rd_en)); + wr_implies_rd_cache.insert({key, res}); + return res; + } + + bool get_wr_excludes_rd(int wpidx, int rpidx) { + auto key = std::make_pair(wpidx, rpidx); + auto it = wr_excludes_rd_cache.find(key); + if (it != wr_excludes_rd_cache.end()) + return it->second; + int wr_en = get_wr_en(wpidx); + int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]); + qcsat.prepare(); + bool res = !qcsat.ez->solve(wr_en, rd_en); + wr_excludes_rd_cache.insert({key, res}); + return res; + } + + bool get_wr_excludes_srst(int wpidx, int rpidx) { + auto key = std::make_pair(wpidx, rpidx); + auto it = wr_excludes_srst_cache.find(key); + if (it != wr_excludes_srst_cache.end()) + return it->second; + int wr_en = get_wr_en(wpidx); + int srst = qcsat.importSigBit(mem.rd_ports[rpidx].srst); + if (mem.rd_ports[rpidx].ce_over_srst) { + int rd_en = qcsat.importSigBit(mem.rd_ports[rpidx].en[0]); + srst = qcsat.ez->AND(srst, rd_en); + } + qcsat.prepare(); + bool res = !qcsat.ez->solve(wr_en, srst); + wr_excludes_srst_cache.insert({key, res}); + return res; + } + + void dump_configs(int stage); + void dump_config(MemConfig &cfg); + void determine_style(); + bool determine_logic_ok(); + bool check_ram_kind(const Ram &ram); + bool check_ram_style(const Ram &ram); + bool check_init(const Ram &ram); + void assign_wr_ports(); + void assign_rd_ports(); + void handle_trans(); + void handle_priority(); + void handle_rd_rst(); + void score_emu_ports(); + void handle_geom(); + void prune_post_geom(); + void emit_port(const MemConfig &cfg, std::vector<Cell*> &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector<int> &hw_addr_swizzle); + void emit(const MemConfig &cfg); +}; + +void MemMapping::dump_configs(int stage) { + const char *stage_name; + switch (stage) { + case 0: + stage_name = "post-geometry"; + break; + case 1: + stage_name = "after post-geometry prune"; + break; + default: + abort(); + } + log_debug("Memory %s.%s mapping candidates (%s):\n", log_id(mem.module->name), log_id(mem.memid), stage_name); + if (logic_ok) { + log_debug("- logic fallback\n"); + log_debug(" - cost: %f\n", logic_cost); + } + for (auto &cfg: cfgs) { + dump_config(cfg); + } +} + +void MemMapping::dump_config(MemConfig &cfg) { + log_debug("- %s:\n", log_id(cfg.def->id)); + for (auto &it: cfg.def->options) + log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second)); + log_debug(" - emulation score: %d\n", cfg.score_emu); + log_debug(" - replicates (for ports): %d\n", cfg.repl_port); + log_debug(" - replicates (for data): %d\n", cfg.repl_d); + log_debug(" - mux score: %d\n", cfg.score_mux); + log_debug(" - demux score: %d\n", cfg.score_demux); + log_debug(" - cost: %f\n", cfg.cost); + std::stringstream os; + for (int x: cfg.def->dbits) + os << " " << x; + std::string dbits_s = os.str(); + log_debug(" - abits %d dbits%s\n", cfg.def->abits, dbits_s.c_str()); + if (cfg.def->byte != 0) + log_debug(" - byte width %d\n", cfg.def->byte); + log_debug(" - chosen base width %d\n", cfg.def->dbits[cfg.base_width_log2]); + os.str(""); + for (int x: cfg.swizzle) + if (x == -1) + os << " -"; + else + os << " " << x; + std::string swizzle_s = os.str(); + log_debug(" - swizzle%s\n", swizzle_s.c_str()); + os.str(""); + for (int i = 0; (1 << i) <= cfg.hard_wide_mask; i++) + if (cfg.hard_wide_mask & 1 << i) + os << " " << i; + std::string wide_s = os.str(); + if (cfg.hard_wide_mask) + log_debug(" - hard wide bits%s\n", wide_s.c_str()); + if (cfg.emu_read_first) + log_debug(" - emulate read-first behavior\n"); + for (int i = 0; i < GetSize(mem.wr_ports); i++) { + auto &pcfg = cfg.wr_ports[i]; + if (pcfg.rd_port == -1) + log_debug(" - write port %d: port group %s\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str()); + else + log_debug(" - write port %d: port group %s (shared with read port %d)\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str(), pcfg.rd_port); + + for (auto &it: pcfg.def->options) + log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second)); + if (cfg.def->width_mode == WidthMode::PerPort) { + std::stringstream os; + for (int i = pcfg.def->min_wr_wide_log2; i <= pcfg.def->max_wr_wide_log2; i++) + os << " " << cfg.def->dbits[i]; + std::string widths_s = os.str(); + const char *note = ""; + if (pcfg.rd_port != -1) + note = pcfg.def->width_tied ? " (tied)" : " (independent)"; + log_debug(" - widths%s%s\n", widths_s.c_str(), note); + } + for (auto i: pcfg.emu_prio) + log_debug(" - emulate priority over write port %d\n", i); + } + for (int i = 0; i < GetSize(mem.rd_ports); i++) { + auto &pcfg = cfg.rd_ports[i]; + if (pcfg.wr_port == -1) + log_debug(" - read port %d: port group %s\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str()); + else + log_debug(" - read port %d: port group %s (shared with write port %d)\n", i, cfg.def->port_groups[pcfg.port_group].names[0].c_str(), pcfg.wr_port); + for (auto &it: pcfg.def->options) + log_debug(" - option %s %s\n", it.first.c_str(), log_const(it.second)); + if (cfg.def->width_mode == WidthMode::PerPort) { + std::stringstream os; + for (int i = pcfg.def->min_rd_wide_log2; i <= pcfg.def->max_rd_wide_log2; i++) + os << " " << cfg.def->dbits[i]; + std::string widths_s = os.str(); + const char *note = ""; + if (pcfg.wr_port != -1) + note = pcfg.def->width_tied ? " (tied)" : " (independent)"; + log_debug(" - widths%s%s\n", widths_s.c_str(), note); + } + if (pcfg.emu_sync) + log_debug(" - emulate data register\n"); + if (pcfg.emu_en) + log_debug(" - emulate clock enable\n"); + if (pcfg.emu_arst) + log_debug(" - emulate async reset\n"); + if (pcfg.emu_srst) + log_debug(" - emulate sync reset\n"); + if (pcfg.emu_init) + log_debug(" - emulate init value\n"); + if (pcfg.emu_srst_en_prio) + log_debug(" - emulate sync reset / enable priority\n"); + for (auto i: pcfg.emu_trans) + log_debug(" - emulate transparency with write port %d\n", i); + } +} + +// Go through memory attributes to determine user-requested mapping style. +void MemMapping::determine_style() { + kind = RamKind::Auto; + style = ""; + if (mem.get_bool_attribute(ID::lram)) { + kind = RamKind::Huge; + return; + } + for (auto attr: {ID::ram_block, ID::rom_block, ID::ram_style, ID::rom_style, ID::ramstyle, ID::romstyle, ID::syn_ramstyle, ID::syn_romstyle}) { + if (mem.has_attribute(attr)) { + Const val = mem.attributes.at(attr); + if (val == 1) { + kind = RamKind::NotLogic; + return; + } + std::string val_s = val.decode_string(); + for (auto &c: val_s) + c = std::tolower(c); + if (val_s == "auto") { + // Nothing. + } else if (val_s == "logic" || val_s == "registers") { + kind = RamKind::Logic; + } else if (val_s == "distributed") { + kind = RamKind::Distributed; + } else if (val_s == "block" || val_s == "block_ram" || val_s == "ebr") { + kind = RamKind::Block; + } else if (val_s == "huge" || val_s == "ultra") { + kind = RamKind::Huge; + } else { + kind = RamKind::NotLogic; + style = val_s; + } + return; + } + } + if (mem.get_bool_attribute(ID::logic_block)) + kind = RamKind::Logic; +} + +// Determine whether the memory can be mapped entirely to soft logic. +bool MemMapping::determine_logic_ok() { + if (kind != RamKind::Auto && kind != RamKind::Logic) + return false; + // Memory is mappable entirely to soft logic iff all its write ports are in the same clock domain. + if (mem.wr_ports.empty()) + return true; + for (auto &port: mem.wr_ports) { + if (!port.clk_enable) + return false; + if (port.clk != mem.wr_ports[0].clk) + return false; + if (port.clk_polarity != mem.wr_ports[0].clk_polarity) + return false; + } + return true; +} + +// Apply RAM kind restrictions (logic/distributed/block/huge), if any. +bool MemMapping::check_ram_kind(const Ram &ram) { + if (style != "") + return true; + if (ram.kind == kind) + return true; + if (kind == RamKind::Auto || kind == RamKind::NotLogic) { + if (ram.kind == RamKind::Distributed && opts.no_auto_distributed) + return false; + if (ram.kind == RamKind::Block && opts.no_auto_block) + return false; + if (ram.kind == RamKind::Huge && opts.no_auto_huge) + return false; + return true; + } + return false; +} + +// Apply specific RAM style restrictions, if any. +bool MemMapping::check_ram_style(const Ram &ram) { + if (style == "") + return true; + for (auto &s: ram.style) + if (s == style) + return true; + return false; +} + +// Handle memory initializer restrictions, if any. +bool MemMapping::check_init(const Ram &ram) { + bool has_nonx = false; + bool has_one = false; + + for (auto &init: mem.inits) { + if (init.data.is_fully_undef()) + continue; + has_nonx = true; + for (auto bit: init.data) + if (bit == State::S1) + has_one = true; + } + + switch (ram.init) { + case MemoryInitKind::None: + return !has_nonx; + case MemoryInitKind::Zero: + return !has_one; + default: + return true; + } +} + +bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_polarity) { + if (def.clk_shared == -1) + return true; + auto &cdef = cfg.def->shared_clocks[def.clk_shared]; + auto &ccfg = cfg.shared_clocks[def.clk_shared]; + if (cdef.anyedge) { + if (!ccfg.used) { + ccfg.used = true; + ccfg.clk = clk; + ccfg.polarity = clk_polarity; + return true; + } else { + return ccfg.clk == clk && ccfg.polarity == clk_polarity; + } + } else { + bool invert = clk_polarity ^ (def.clk_pol == ClkPolKind::Posedge); + if (!ccfg.used) { + ccfg.used = true; + ccfg.clk = clk; + ccfg.invert = invert; + return true; + } else { + return ccfg.clk == clk && ccfg.invert == invert; + } + } +} + +// Perform write port assignment, validating clock options as we go. +void MemMapping::assign_wr_ports() { + for (auto &port: mem.wr_ports) { + if (!port.clk_enable) { + // Async write ports not supported. + cfgs.clear(); + return; + } + MemConfigs new_cfgs; + for (auto &cfg: cfgs) { + for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) { + auto &pg = cfg.def->port_groups[pgi]; + // Make sure the target port group still has a free port. + int used = 0; + for (auto &oport: cfg.wr_ports) + if (oport.port_group == pgi) + used++; + if (used >= GetSize(pg.names)) + continue; + for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) { + auto &def = pg.variants[pvi]; + // Make sure the target is a write port. + if (def.kind == PortKind::Ar || def.kind == PortKind::Sr) + continue; + MemConfig new_cfg = cfg; + WrPortConfig pcfg; + pcfg.rd_port = -1; + pcfg.port_group = pgi; + pcfg.port_variant = pvi; + pcfg.def = &def; + if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity)) + continue; + new_cfg.wr_ports.push_back(pcfg); + new_cfgs.push_back(new_cfg); + } + } + } + cfgs = new_cfgs; + } +} + +// Perform read port assignment, validating clock and rden options as we go. +void MemMapping::assign_rd_ports() { + for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { + auto &port = mem.rd_ports[pidx]; + MemConfigs new_cfgs; + for (auto &cfg: cfgs) { + // First pass: read port not shared with a write port. + for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) { + auto &pg = cfg.def->port_groups[pgi]; + // Make sure the target port group has a port not used up by write ports. + // Overuse by other read ports is not a problem — this will just result + // in memory duplication. + int used = 0; + for (auto &oport: cfg.wr_ports) + if (oport.port_group == pgi) + used++; + if (used >= GetSize(pg.names)) + continue; + for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) { + auto &def = pg.variants[pvi]; + // Make sure the target is a read port. + if (def.kind == PortKind::Sw) + continue; + // If mapping an async port, accept only async defs. + if (!port.clk_enable) { + if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw) + continue; + } + MemConfig new_cfg = cfg; + RdPortConfig pcfg; + pcfg.wr_port = -1; + pcfg.port_group = pgi; + pcfg.port_variant = pvi; + pcfg.def = &def; + if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw) { + pcfg.emu_sync = false; + if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity)) + continue; + // Decide if rden is usable. + if (port.en != State::S1) { + if (def.clk_en) { + pcfg.rd_en_to_clk_en = true; + } else { + pcfg.emu_en = !def.rd_en; + } + } + } else { + pcfg.emu_sync = port.clk_enable; + } + new_cfg.rd_ports.push_back(pcfg); + new_cfgs.push_back(new_cfg); + } + } + // Second pass: read port shared with a write port. + for (int wpidx = 0; wpidx < GetSize(mem.wr_ports); wpidx++) { + auto &wport = mem.wr_ports[wpidx]; + auto &wpcfg = cfg.wr_ports[wpidx]; + auto &def = *wpcfg.def; + // Make sure the write port is not yet shared. + if (wpcfg.rd_port != -1) + continue; + // Make sure the target is a read port. + if (def.kind == PortKind::Sw) + continue; + // Validate address compatibility. + if (!addr_compatible(wpidx, pidx)) + continue; + // Validate clock compatibility, if needed. + if (def.kind == PortKind::Srsw) { + if (!port.clk_enable) + continue; + if (port.clk != wport.clk) + continue; + if (port.clk_polarity != wport.clk_polarity) + continue; + } + // Okay, let's fill it in. + MemConfig new_cfg = cfg; + new_cfg.wr_ports[wpidx].rd_port = pidx; + RdPortConfig pcfg; + pcfg.wr_port = wpidx; + pcfg.port_group = wpcfg.port_group; + pcfg.port_variant = wpcfg.port_variant; + pcfg.def = wpcfg.def; + pcfg.emu_sync = port.clk_enable && def.kind == PortKind::Arsw; + // For srsw, check rden capability. + if (def.kind == PortKind::Srsw) { + bool trans = port.transparency_mask[wpidx]; + bool col_x = port.collision_x_mask[wpidx]; + if (def.rdwr == RdWrKind::NoChange) { + if (!get_wr_excludes_rd(wpidx, pidx)) { + if (!trans && !col_x) + continue; + if (trans) + pcfg.emu_trans.push_back(wpidx); + new_cfg.wr_ports[wpidx].force_uniform = true; + } + if (port.en != State::S1) { + if (def.clk_en) { + pcfg.rd_en_to_clk_en = true; + } else { + pcfg.emu_en = !def.rd_en; + } + } + } else { + if (!col_x && !trans && def.rdwr != RdWrKind::Old) + continue; + if (trans) { + if (def.rdwr != RdWrKind::New && def.rdwr != RdWrKind::NewOnly) + pcfg.emu_trans.push_back(wpidx); + } + if (def.rdwr == RdWrKind::NewOnly) { + if (!get_wr_excludes_rd(wpidx, pidx)) + new_cfg.wr_ports[wpidx].force_uniform = true; + } + if (port.en != State::S1) { + if (def.clk_en) { + if (get_wr_implies_rd(wpidx, pidx)) { + pcfg.rd_en_to_clk_en = true; + } else { + pcfg.emu_en = !def.rd_en; + } + } else { + pcfg.emu_en = !def.rd_en; + } + } + } + } + new_cfg.rd_ports.push_back(pcfg); + new_cfgs.push_back(new_cfg); + } + } + cfgs = new_cfgs; + } +} + +// Validate transparency restrictions, determine where to add soft transparency logic. +void MemMapping::handle_trans() { + if (mem.emulate_read_first_ok()) { + MemConfigs new_cfgs; + for (auto &cfg: cfgs) { + new_cfgs.push_back(cfg); + bool ok = true; + // Using this trick will break read-write port sharing. + for (auto &pcfg: cfg.rd_ports) + if (pcfg.wr_port != -1) + ok = false; + if (ok) { + cfg.emu_read_first = true; + new_cfgs.push_back(cfg); + } + } + cfgs = new_cfgs; + } + for (int rpidx = 0; rpidx < GetSize(mem.rd_ports); rpidx++) { + auto &rport = mem.rd_ports[rpidx]; + if (!rport.clk_enable) + continue; + for (int wpidx = 0; wpidx < GetSize(mem.wr_ports); wpidx++) { + auto &wport = mem.wr_ports[wpidx]; + if (!wport.clk_enable) + continue; + if (rport.clk != wport.clk) + continue; + if (rport.clk_polarity != wport.clk_polarity) + continue; + // If we got this far, we have a transparency restriction + // to uphold. + MemConfigs new_cfgs; + for (auto &cfg: cfgs) { + auto &rpcfg = cfg.rd_ports[rpidx]; + auto &wpcfg = cfg.wr_ports[wpidx]; + // The transparency relation for shared ports already handled while assigning them. + if (rpcfg.wr_port == wpidx) { + new_cfgs.push_back(cfg); + continue; + } + if (rport.collision_x_mask[wpidx] && !cfg.emu_read_first) { + new_cfgs.push_back(cfg); + continue; + } + bool transparent = rport.transparency_mask[wpidx] || cfg.emu_read_first; + if (rpcfg.emu_sync) { + // For async read port, just add the transparency logic + // if necessary. + if (transparent) + rpcfg.emu_trans.push_back(wpidx); + new_cfgs.push_back(cfg); + } else { + // Otherwise, split through the relevant wrtrans caps. + // For non-transparent ports, the cap needs to be present. + // For transparent ports, we can emulate transparency + // even without a direct cap. + bool found = false; + for (auto &tdef: wpcfg.def->wrtrans) { + // Check if the target matches. + if (tdef.target_kind == WrTransTargetKind::Group && rpcfg.port_group != tdef.target_group) + continue; + // Check if the transparency kind is acceptable. + if (transparent) { + if (tdef.kind == WrTransKind::Old) + continue; + } else { + if (tdef.kind != WrTransKind::Old) + continue; + } + // Okay, we can use this cap. + new_cfgs.push_back(cfg); + found = true; + break; + } + if (!found && transparent) { + // If the port pair is transparent, but no cap was + // found, use emulation. + rpcfg.emu_trans.push_back(wpidx); + new_cfgs.push_back(cfg); + } + } + } + cfgs = new_cfgs; + } + } +} + +// Determine where to add soft priority logic. +void MemMapping::handle_priority() { + for (int p1idx = 0; p1idx < GetSize(mem.wr_ports); p1idx++) { + for (int p2idx = 0; p2idx < GetSize(mem.wr_ports); p2idx++) { + auto &port2 = mem.wr_ports[p2idx]; + if (!port2.priority_mask[p1idx]) + continue; + for (auto &cfg: cfgs) { + auto &p1cfg = cfg.rd_ports[p1idx]; + auto &p2cfg = cfg.wr_ports[p2idx]; + bool found = false; + for (auto &pgi: p2cfg.def->wrprio) { + if (pgi == p1cfg.port_group) { + found = true; + break; + } + } + // If no cap was found, emulate. + if (!found) + p2cfg.emu_prio.push_back(p1idx); + } + } + } +} + +bool is_all_zero(const Const &val) { + for (auto bit: val.bits) + if (bit == State::S1) + return false; + return true; +} + +// Determine where to add soft init value / reset logic. +void MemMapping::handle_rd_rst() { + for (auto &cfg: cfgs) { + for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { + auto &port = mem.rd_ports[pidx]; + auto &pcfg = cfg.rd_ports[pidx]; + // Only sync ports are relevant. + // If emulated by async port or we already emulate CE, init will be + // included for free. + if (!port.clk_enable || pcfg.emu_sync || pcfg.emu_en) + continue; + switch (pcfg.def->rdinitval) { + case ResetValKind::None: + pcfg.emu_init = !port.init_value.is_fully_undef(); + break; + case ResetValKind::Zero: + pcfg.emu_init = !is_all_zero(port.init_value); + break; + default: + break; + } + Const init_val = port.init_value; + if (port.arst != State::S0) { + switch (pcfg.def->rdarstval) { + case ResetValKind::None: + pcfg.emu_arst = true; + break; + case ResetValKind::Zero: + pcfg.emu_arst = !is_all_zero(port.arst_value); + break; + case ResetValKind::Init: + if (init_val.is_fully_undef()) + init_val = port.arst_value; + pcfg.emu_arst = init_val != port.arst_value; + break; + default: + break; + } + } + if (port.srst != State::S0) { + switch (pcfg.def->rdsrstval) { + case ResetValKind::None: + pcfg.emu_srst = true; + break; + case ResetValKind::Zero: + pcfg.emu_srst = !is_all_zero(port.srst_value); + break; + case ResetValKind::Init: + if (init_val.is_fully_undef()) + init_val = port.srst_value; + pcfg.emu_srst = init_val != port.srst_value; + break; + default: + break; + } + if (!pcfg.emu_srst && pcfg.def->rdsrst_block_wr && pcfg.wr_port != -1) { + if (!get_wr_excludes_srst(pcfg.wr_port, pidx)) + pcfg.emu_srst = true; + } + if (!pcfg.emu_srst && port.en != State::S1) { + if (port.ce_over_srst) { + switch (pcfg.def->rdsrstmode) { + case SrstKind::Ungated: + pcfg.emu_srst_en_prio = true; + break; + case SrstKind::GatedClkEn: + pcfg.emu_srst_en_prio = !pcfg.rd_en_to_clk_en; + break; + case SrstKind::GatedRdEn: + break; + default: + log_assert(0); + } + } else { + switch (pcfg.def->rdsrstmode) { + case SrstKind::Ungated: + break; + case SrstKind::GatedClkEn: + if (pcfg.rd_en_to_clk_en) { + if (pcfg.def->rd_en) { + pcfg.rd_en_to_clk_en = false; + } else { + pcfg.emu_srst_en_prio = true; + } + } + break; + case SrstKind::GatedRdEn: + pcfg.emu_srst_en_prio = true; + break; + default: + log_assert(0); + } + } + } + } else { + if (pcfg.def->rd_en && pcfg.def->rdwr == RdWrKind::NoChange && pcfg.wr_port != -1) { + pcfg.rd_en_to_clk_en = false; + } + } + } + } +} + +void MemMapping::score_emu_ports() { + for (auto &cfg: cfgs) { + std::vector<int> port_usage_wr(cfg.def->port_groups.size()); + std::vector<int> port_usage_rd(cfg.def->port_groups.size()); + int score = 0; + // 3 points for every write port if we need to do read-first emulation. + if (cfg.emu_read_first) + score += 3 * GetSize(cfg.wr_ports); + for (auto &pcfg: cfg.wr_ports) { + // 1 point for every priority relation we need to fix up. + // This is just a gate for every distinct wren pair. + score += GetSize(pcfg.emu_prio); + port_usage_wr[pcfg.port_group]++; + } + for (auto &pcfg: cfg.rd_ports) { + // 3 points for every soft transparency logic instance. This involves + // registers and other major mess. + score += 3 * GetSize(pcfg.emu_trans); + // 3 points for CE soft logic. Likewise involves registers. + // If we already do this, subsumes any init/srst/arst emulation. + if (pcfg.emu_en) + score += 3; + // 2 points for soft init value / reset logic: involves single bit + // register and some muxes. + if (pcfg.emu_init) + score += 2; + if (pcfg.emu_arst) + score += 2; + if (pcfg.emu_srst) + score += 2; + // 1 point for wrong srst/en priority (fixed with a single gate). + if (pcfg.emu_srst_en_prio) + score++; + // 1 point for every non-shared read port used, as a tiebreaker + // to prefer single-port configs. + if (pcfg.wr_port == -1) { + score++; + port_usage_rd[pcfg.port_group]++; + } + } + cfg.score_emu = score; + int repl_port = 1; + for (int i = 0; i < GetSize(cfg.def->port_groups); i++) { + int space = GetSize(cfg.def->port_groups[i].names) - port_usage_wr[i]; + log_assert(space >= 0); + if (port_usage_rd[i] > 0) { + log_assert(space > 0); + int usage = port_usage_rd[i]; + int cur = (usage + space - 1) / space; + if (cur > repl_port) + repl_port = cur; + } + } + cfg.repl_port = repl_port; + } +} + +void MemMapping::handle_geom() { + std::vector<int> wren_size; + for (auto &port: mem.wr_ports) { + SigSpec en = port.en; + en.sort_and_unify(); + wren_size.push_back(GetSize(en)); + } + for (auto &cfg: cfgs) { + // First, create a set of "byte boundaries": the bit positions in source memory word + // that have write enable different from the previous bit in any write port. + // Bit 0 is considered to be a byte boundary as well. + // Likewise, create a set of "word boundaries" that are like above, but only for write ports + // with the "force uniform" flag set. + std::vector<bool> byte_boundary(mem.width, false); + std::vector<bool> word_boundary(mem.width, false); + byte_boundary[0] = true; + for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) { + auto &port = mem.wr_ports[pidx]; + auto &pcfg = cfg.wr_ports[pidx]; + if (pcfg.force_uniform) + word_boundary[0] = true; + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + for (int i = 1; i < mem.width; i++) { + int pos = sub * mem.width + i; + if (port.en[pos] != port.en[pos-1]) { + byte_boundary[i] = true; + if (pcfg.force_uniform) + word_boundary[i] = true; + } + } + } + } + bool got_config = false; + int best_cost = 0; + int byte_width_log2 = 0; + for (int i = 0; i < GetSize(cfg.def->dbits); i++) + if (cfg.def->byte >= cfg.def->dbits[i]) + byte_width_log2 = i; + if (cfg.def->byte == 0) + byte_width_log2 = GetSize(cfg.def->dbits) - 1; + pool<int> no_wide_bits; + // Determine which of the source address bits involved in wide ports + // are "uniform". Bits are considered uniform if, when a port is widened through + // them, the write enables are the same for both values of the bit. + int max_wr_wide_log2 = 0; + for (auto &port: mem.wr_ports) + if (port.wide_log2 > max_wr_wide_log2) + max_wr_wide_log2 = port.wide_log2; + int max_wide_log2 = max_wr_wide_log2; + for (auto &port: mem.rd_ports) + if (port.wide_log2 > max_wide_log2) + max_wide_log2 = port.wide_log2; + int wide_nu_start = max_wide_log2; + int wide_nu_end = max_wr_wide_log2; + for (int i = 0; i < GetSize(mem.wr_ports); i++) { + auto &port = mem.wr_ports[i]; + auto &pcfg = cfg.wr_ports[i]; + for (int j = 0; j < port.wide_log2; j++) { + bool uniform = true; + // If write enables don't match, mark bit as non-uniform. + for (int k = 0; k < (1 << port.wide_log2); k += 2 << j) + if (port.en.extract(k * mem.width, mem.width << j) != port.en.extract((k + (1 << j)) * mem.width, mem.width << j)) + uniform = false; + if (!uniform) { + if (pcfg.force_uniform) { + for (int k = j; k < port.wide_log2; k++) + no_wide_bits.insert(k); + } + if (j < wide_nu_start) + wide_nu_start = j; + break; + } + } + if (pcfg.def->width_tied && pcfg.rd_port != -1) { + // If: + // + // - the write port is merged with a read port + // - the read port is wider than the write port + // - read and write widths are tied + // + // then we will have to artificially widen the write + // port to the width of the read port, and emulate + // a narrower write path by use of write enables, + // which will definitely be non-uniform over the added + // bits. + auto &rport = mem.rd_ports[pcfg.rd_port]; + if (rport.wide_log2 > port.wide_log2) { + if (port.wide_log2 < wide_nu_start) + wide_nu_start = port.wide_log2; + if (rport.wide_log2 > wide_nu_end) + wide_nu_end = rport.wide_log2; + if (pcfg.force_uniform) { + for (int k = port.wide_log2; k < rport.wide_log2; k++) + no_wide_bits.insert(k); + } + } + } + } + // Iterate over base widths. + for (int base_width_log2 = 0; base_width_log2 < GetSize(cfg.def->dbits); base_width_log2++) { + // Now, see how many data bits we actually have available. + // This is usually dbits[base_width_log2], but could be smaller if we + // ran afoul of a max width limitation. Configurations where this + // happens are not useful, unless we need it to satisfy a *minimum* + // width limitation. + int unit_width_log2 = base_width_log2; + for (auto &pcfg: cfg.wr_ports) + if (unit_width_log2 > pcfg.def->max_wr_wide_log2) + unit_width_log2 = pcfg.def->max_wr_wide_log2; + for (auto &pcfg: cfg.rd_ports) + if (unit_width_log2 > pcfg.def->max_rd_wide_log2) + unit_width_log2 = pcfg.def->max_rd_wide_log2; + if (unit_width_log2 != base_width_log2 && got_config) + break; + int unit_width = cfg.def->dbits[unit_width_log2]; + // Also determine effective byte width (the granularity of write enables). + int effective_byte = cfg.def->byte; + if (effective_byte == 0 || effective_byte > unit_width) + effective_byte = unit_width; + if (mem.wr_ports.empty()) + effective_byte = 1; + log_assert(unit_width % effective_byte == 0); + // Create the swizzle pattern. + std::vector<int> swizzle; + for (int i = 0; i < mem.width; i++) { + if (word_boundary[i]) + while (GetSize(swizzle) % unit_width) + swizzle.push_back(-1); + else if (byte_boundary[i]) + while (GetSize(swizzle) % effective_byte) + swizzle.push_back(-1); + swizzle.push_back(i); + } + if (word_boundary[0]) + while (GetSize(swizzle) % unit_width) + swizzle.push_back(-1); + else + while (GetSize(swizzle) % effective_byte) + swizzle.push_back(-1); + // Now evaluate the configuration, then keep adding more hard wide bits + // and evaluating. + int hard_wide_mask = 0; + int hard_wide_num = 0; + bool byte_failed = false; + while (1) { + // Check if all min width constraints are satisfied. + // Only check these constraints for write ports with width below + // byte width — for other ports, we can emulate narrow width with + // a larger one. + bool min_width_ok = true; + int min_width_bit = wide_nu_start; + for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) { + auto &port = mem.wr_ports[pidx]; + int w = base_width_log2; + for (int i = 0; i < port.wide_log2; i++) + if (hard_wide_mask & 1 << i) + w++; + if (w < cfg.wr_ports[pidx].def->min_wr_wide_log2 && w < byte_width_log2) { + min_width_ok = false; + if (min_width_bit > port.wide_log2) + min_width_bit = port.wide_log2; + } + } + if (min_width_ok) { + int emu_wide_bits = max_wide_log2 - hard_wide_num; + int mult_wide = 1 << emu_wide_bits; + int addrs = 1 << (cfg.def->abits - base_width_log2 + emu_wide_bits); + int min_addr = mem.start_offset / addrs; + int max_addr = (mem.start_offset + mem.size - 1) / addrs; + int mult_a = max_addr - min_addr + 1; + int bits = mult_a * mult_wide * GetSize(swizzle); + int repl = (bits + unit_width - 1) / unit_width; + int score_demux = 0; + for (int i = 0; i < GetSize(mem.wr_ports); i++) { + auto &port = mem.wr_ports[i]; + int w = emu_wide_bits; + for (int i = 0; i < port.wide_log2; i++) + if (!(hard_wide_mask & 1 << i)) + w--; + if (w || mult_a != 1) + score_demux += (mult_a << w) * wren_size[i]; + } + int score_mux = 0; + for (auto &port: mem.rd_ports) { + int w = emu_wide_bits; + for (int i = 0; i < port.wide_log2; i++) + if (!(hard_wide_mask & 1 << i)) + w--; + score_mux += ((mult_a << w) - 1) * GetSize(port.data); + } + double cost = (cfg.def->cost - cfg.def->widthscale) * repl * cfg.repl_port; + cost += cfg.def->widthscale * mult_a * mult_wide * mem.width / unit_width * cfg.repl_port; + cost += score_mux * FACTOR_MUX; + cost += score_demux * FACTOR_DEMUX; + cost += cfg.score_emu * FACTOR_EMU; + if (!got_config || cost < best_cost) { + cfg.base_width_log2 = base_width_log2; + cfg.unit_width_log2 = unit_width_log2; + cfg.swizzle = swizzle; + cfg.hard_wide_mask = hard_wide_mask; + cfg.emu_wide_mask = ((1 << max_wide_log2) - 1) & ~hard_wide_mask; + cfg.repl_d = repl; + cfg.score_demux = score_demux; + cfg.score_mux = score_mux; + cfg.cost = cost; + best_cost = cost; + got_config = true; + } + } + if (cfg.def->width_mode != WidthMode::PerPort) + break; + // Now, pick the next bit to add to the hard wide mask. +next_hw: + int scan_from; + int scan_to; + bool retry = false; + if (!min_width_ok) { + // If we still haven't met the minimum width limits, + // add the highest one that will be useful for working + // towards all unmet limits. + scan_from = min_width_bit; + scan_to = 0; + // If the relevant write port is not wide, it's impossible. + } else if (byte_failed) { + // If we already failed with uniformly-written bits only, + // go with uniform bits that are only involved in reads. + scan_from = max_wide_log2; + scan_to = wide_nu_end; + } else if (base_width_log2 + hard_wide_num < byte_width_log2) { + // If we still need uniform bits, prefer the low ones. + scan_from = wide_nu_start; + scan_to = 0; + retry = true; + } else { + scan_from = max_wide_log2; + scan_to = 0; + } + int bit = scan_from - 1; + while (1) { + if (bit < scan_to) { +hw_bit_failed: + if (retry) { + byte_failed = true; + goto next_hw; + } else { + goto bw_done; + } + } + if (!(hard_wide_mask & 1 << bit) && !no_wide_bits.count(bit)) + break; + bit--; + } + int new_hw_mask = hard_wide_mask | 1 << bit; + // Check if all max width constraints are satisfied. + for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) { + auto &port = mem.wr_ports[pidx]; + int w = base_width_log2; + for (int i = 0; i < port.wide_log2; i++) + if (new_hw_mask & 1 << i) + w++; + if (w > cfg.wr_ports[pidx].def->max_wr_wide_log2) { + goto hw_bit_failed; + } + } + for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { + auto &port = mem.rd_ports[pidx]; + int w = base_width_log2; + for (int i = 0; i < port.wide_log2; i++) + if (new_hw_mask & 1 << i) + w++; + if (w > cfg.rd_ports[pidx].def->max_rd_wide_log2) { + goto hw_bit_failed; + } + } + // Bit ok, commit. + hard_wide_mask = new_hw_mask; + hard_wide_num++; + } +bw_done:; + } + log_assert(got_config); + } +} + +void MemMapping::prune_post_geom() { + std::vector<bool> keep; + dict<std::string, int> rsrc; + for (int i = 0; i < GetSize(cfgs); i++) { + auto &cfg = cfgs[i]; + std::string key = cfg.def->resource_name; + if (key.empty()) { + switch (cfg.def->kind) { + case RamKind::Distributed: + key = "[distributed]"; + break; + case RamKind::Block: + key = "[block]"; + break; + case RamKind::Huge: + key = "[huge]"; + break; + default: + break; + } + } + auto it = rsrc.find(key); + if (it == rsrc.end()) { + rsrc[key] = i; + keep.push_back(true); + } else { + auto &ocfg = cfgs[it->second]; + if (cfg.cost < ocfg.cost) { + keep[it->second] = false; + it->second = i; + keep.push_back(true); + } else { + keep.push_back(false); + } + } + } + MemConfigs new_cfgs; + for (int i = 0; i < GetSize(cfgs); i++) + if (keep[i]) + new_cfgs.push_back(cfgs[i]); + cfgs = new_cfgs; +} + +Swizzle gen_swizzle(const Mem &mem, const MemConfig &cfg, int sw_wide_log2, int hw_wide_log2) { + Swizzle res; + + std::vector<int> emu_wide_bits; + std::vector<int> hard_wide_bits; + for (int i = 0; i < ceil_log2(mem.size); i++) { + if (cfg.emu_wide_mask & 1 << i) + emu_wide_bits.push_back(i); + else if (GetSize(hard_wide_bits) < hw_wide_log2 - cfg.base_width_log2) + hard_wide_bits.push_back(i); + } + for (int x : hard_wide_bits) + if (x >= sw_wide_log2) + res.addr_mux_bits.push_back(x); + for (int x : emu_wide_bits) + if (x >= sw_wide_log2) + res.addr_mux_bits.push_back(x); + + res.addr_shift = cfg.def->abits - cfg.base_width_log2 + GetSize(emu_wide_bits); + res.addr_start = mem.start_offset & ~((1 << res.addr_shift) - 1); + res.addr_end = ((mem.start_offset + mem.size - 1) | ((1 << res.addr_shift) - 1)) + 1; + int hnum = (res.addr_end - res.addr_start) >> res.addr_shift; + int unit_width = cfg.def->dbits[cfg.unit_width_log2]; + + for (int rd = 0; rd < cfg.repl_d; rd++) { + std::vector<SwizzleBit> bits(cfg.def->dbits[hw_wide_log2]); + for (auto &bit: bits) + bit.valid = false; + res.bits.push_back(bits); + } + + for (int hi = 0; hi < hnum; hi++) { + for (int ewi = 0; ewi < (1 << GetSize(emu_wide_bits)); ewi++) { + for (int hwi = 0; hwi < (1 << GetSize(hard_wide_bits)); hwi++) { + int mux_idx = 0; + int sub = 0; + int mib = 0; + int hbit_base = 0; + for (int i = 0; i < GetSize(hard_wide_bits); i++) { + if (hard_wide_bits[i] < sw_wide_log2) { + if (hwi & 1 << i) + sub |= 1 << hard_wide_bits[i]; + } else { + if (hwi & 1 << i) + mux_idx |= 1 << mib; + mib++; + } + if (hwi & 1 << i) + hbit_base += cfg.def->dbits[i + cfg.base_width_log2]; + } + for (int i = 0; i < GetSize(emu_wide_bits); i++) { + if (emu_wide_bits[i] < sw_wide_log2) { + if (ewi & 1 << i) + sub |= 1 << emu_wide_bits[i]; + } else { + if (ewi & 1 << i) + mux_idx |= 1 << mib; + mib++; + } + } + mux_idx |= hi << mib; + int addr = res.addr_start + (hi << res.addr_shift); + for (int i = 0; i < GetSize(res.addr_mux_bits); i++) + if (mux_idx & 1 << i) + addr += 1 << res.addr_mux_bits[i]; + for (int bit = 0; bit < GetSize(cfg.swizzle); bit++) { + if (cfg.swizzle[bit] == -1) + continue; + int rbit = bit + GetSize(cfg.swizzle) * (ewi + (hi << GetSize(emu_wide_bits))); + int rep = rbit / unit_width; + int hbit = hbit_base + rbit % unit_width; + auto &swz = res.bits[rep][hbit]; + swz.valid = true; + swz.addr = addr; + swz.mux_idx = mux_idx; + swz.bit = cfg.swizzle[bit] + sub * mem.width; + } + } + } + } + + return res; +} + +void clean_undef(std::vector<State> &val) { + for (auto &bit : val) + if (bit != State::S1) + bit = State::S0; +} + +std::vector<SigSpec> generate_demux(Mem &mem, int wpidx, const Swizzle &swz) { + auto &port = mem.wr_ports[wpidx]; + std::vector<SigSpec> res; + int hi_bits = ceil_log2(swz.addr_end - swz.addr_start) - swz.addr_shift; + auto compressed = port.compress_en(); + SigSpec sig_a = compressed.first; + SigSpec addr = port.addr; + if (GetSize(addr) > hi_bits + swz.addr_shift) { + int lo = mem.start_offset; + int hi = mem.start_offset + mem.size; + int bits = ceil_log2(hi); + for (int i = 0; i < bits; i++) { + int new_lo = lo; + if (lo & 1 << i) + new_lo -= 1 << i; + int new_hi = hi; + if (hi & 1 << i) + new_hi += 1 << i; + if (new_hi - new_lo > (1 << (hi_bits + swz.addr_shift))) + break; + lo = new_lo; + hi = new_hi; + } + SigSpec in_range = mem.module->And(NEW_ID, mem.module->Ge(NEW_ID, addr, lo), mem.module->Lt(NEW_ID, addr, hi)); + sig_a = mem.module->Mux(NEW_ID, Const(State::S0, GetSize(sig_a)), sig_a, in_range); + } + addr.extend_u0(swz.addr_shift + hi_bits, false); + SigSpec sig_s; + for (int x : swz.addr_mux_bits) + sig_s.append(addr[x]); + for (int i = 0; i < hi_bits; i++) + sig_s.append(addr[swz.addr_shift + i]); + SigSpec sig_y; + if (GetSize(sig_s) == 0) + sig_y = sig_a; + else + sig_y = mem.module->Demux(NEW_ID, sig_a, sig_s); + for (int i = 0; i < ((swz.addr_end - swz.addr_start) >> swz.addr_shift); i++) { + for (int j = 0; j < (1 << GetSize(swz.addr_mux_bits)); j++) { + int hi = ((swz.addr_start >> swz.addr_shift) + i) & ((1 << hi_bits) - 1); + int pos = (hi << GetSize(swz.addr_mux_bits) | j) * GetSize(sig_a); + res.push_back(port.decompress_en(compressed.second, sig_y.extract(pos, GetSize(sig_a)))); + } + } + return res; +} + +std::vector<SigSpec> generate_mux(Mem &mem, int rpidx, const Swizzle &swz) { + auto &port = mem.rd_ports[rpidx]; + std::vector<SigSpec> res; + int hi_bits = ceil_log2(swz.addr_end - swz.addr_start) - swz.addr_shift; + SigSpec sig_s; + SigSpec addr = port.addr; + addr.extend_u0(swz.addr_shift + hi_bits, false); + for (int x : swz.addr_mux_bits) + sig_s.append(addr[x]); + for (int i = 0; i < hi_bits; i++) + sig_s.append(addr[swz.addr_shift + i]); + if (GetSize(sig_s) == 0) { + return {port.data}; + } + if (port.clk_enable) { + SigSpec new_sig_s = mem.module->addWire(NEW_ID, GetSize(sig_s)); + mem.module->addDffe(NEW_ID, port.clk, port.en, sig_s, new_sig_s, port.clk_polarity); + sig_s = new_sig_s; + } + SigSpec sig_a = Const(State::Sx, GetSize(port.data) << hi_bits << GetSize(swz.addr_mux_bits)); + for (int i = 0; i < ((swz.addr_end - swz.addr_start) >> swz.addr_shift); i++) { + for (int j = 0; j < (1 << GetSize(swz.addr_mux_bits)); j++) { + SigSpec sig = mem.module->addWire(NEW_ID, GetSize(port.data)); + int hi = ((swz.addr_start >> swz.addr_shift) + i) & ((1 << hi_bits) - 1); + int pos = (hi << GetSize(swz.addr_mux_bits) | j) * GetSize(port.data); + for (int k = 0; k < GetSize(port.data); k++) + sig_a[pos + k] = sig[k]; + res.push_back(sig); + } + } + mem.module->addBmux(NEW_ID, sig_a, sig_s, port.data); + return res; +} + +void MemMapping::emit_port(const MemConfig &cfg, std::vector<Cell*> &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector<int> &hw_addr_swizzle) { + for (auto &it: pdef.options) + for (auto cell: cells) + cell->setParam(stringf("\\PORT_%s_OPTION_%s", name, it.first.c_str()), it.second); + SigSpec addr = Const(State::Sx, cfg.def->abits); + int wide_log2 = 0, wr_wide_log2 = 0, rd_wide_log2 = 0; + SigSpec clk = State::S0; + SigSpec clk_en = State::S0; + bool clk_pol = true; + if (wpidx != -1) { + auto &wport = mem.wr_ports[wpidx]; + clk = wport.clk; + clk_pol = wport.clk_polarity; + addr = wport.addr; + wide_log2 = wr_wide_log2 = wport.wide_log2; + if (rpidx != -1) { + auto &rport = mem.rd_ports[rpidx]; + auto &rpcfg = cfg.rd_ports[rpidx]; + rd_wide_log2 = rport.wide_log2; + if (rd_wide_log2 > wr_wide_log2) + wide_log2 = rd_wide_log2; + else + addr = rport.addr; + if (pdef.clk_en) { + if (rpcfg.rd_en_to_clk_en) { + if (pdef.rdwr == RdWrKind::NoChange) { + clk_en = mem.module->Or(NEW_ID, rport.en, mem.module->ReduceOr(NEW_ID, wport.en)); + } else { + clk_en = rport.en; + } + } else { + clk_en = State::S1; + } + } + } else { + if (pdef.clk_en) + clk_en = State::S1; + } + } else if (rpidx != -1) { + auto &rport = mem.rd_ports[rpidx]; + auto &rpcfg = cfg.rd_ports[rpidx]; + if (rport.clk_enable) { + clk = rport.clk; + clk_pol = rport.clk_polarity; + } + addr = rport.addr; + wide_log2 = rd_wide_log2 = rport.wide_log2; + if (pdef.clk_en) { + if (rpcfg.rd_en_to_clk_en) + clk_en = rport.en; + else + clk_en = State::S1; + } + + } + addr = worker.sigmap_xmux(addr); + if (pdef.kind != PortKind::Ar) { + switch (pdef.clk_pol) { + case ClkPolKind::Posedge: + if (!clk_pol) + clk = mem.module->Not(NEW_ID, clk); + break; + case ClkPolKind::Negedge: + if (clk_pol) + clk = mem.module->Not(NEW_ID, clk); + break; + case ClkPolKind::Anyedge: + for (auto cell: cells) + cell->setParam(stringf("\\PORT_%s_CLK_POL", name), clk_pol); + } + for (auto cell: cells) { + cell->setPort(stringf("\\PORT_%s_CLK", name), clk); + if (pdef.clk_en) + cell->setPort(stringf("\\PORT_%s_CLK_EN", name), clk_en); + } + } + + // Width determination. + if (pdef.width_tied) { + rd_wide_log2 = wr_wide_log2 = wide_log2; + } + int hw_wr_wide_log2 = cfg.base_width_log2; + for (int i = 0; i < wr_wide_log2; i++) + if (cfg.hard_wide_mask & (1 << i)) + hw_wr_wide_log2++; + if (hw_wr_wide_log2 < pdef.min_wr_wide_log2) + hw_wr_wide_log2 = pdef.min_wr_wide_log2; + if (hw_wr_wide_log2 > pdef.max_wr_wide_log2) + hw_wr_wide_log2 = pdef.max_wr_wide_log2; + int hw_rd_wide_log2 = cfg.base_width_log2; + for (int i = 0; i < rd_wide_log2; i++) + if (cfg.hard_wide_mask & (1 << i)) + hw_rd_wide_log2++; + if (hw_rd_wide_log2 < pdef.min_rd_wide_log2) + hw_rd_wide_log2 = pdef.min_rd_wide_log2; + if (hw_rd_wide_log2 > pdef.max_rd_wide_log2) + hw_rd_wide_log2 = pdef.max_rd_wide_log2; + if (pdef.width_tied) { + // For unused ports, pick max available width, + // in case narrow ports require disabling parity + // bits etc. + if (wpidx == -1 && rpidx == -1) { + hw_wr_wide_log2 = pdef.max_wr_wide_log2; + hw_rd_wide_log2 = pdef.max_rd_wide_log2; + } + } else { + if (wpidx == -1) + hw_wr_wide_log2 = pdef.max_wr_wide_log2; + if (rpidx == -1) + hw_rd_wide_log2 = pdef.max_rd_wide_log2; + } + if (cfg.def->width_mode == WidthMode::PerPort) { + for (auto cell: cells) { + if (pdef.width_tied) { + cell->setParam(stringf("\\PORT_%s_WIDTH", name), cfg.def->dbits[hw_wr_wide_log2]); + } else { + if (pdef.kind != PortKind::Ar && pdef.kind != PortKind::Sr) + cell->setParam(stringf("\\PORT_%s_WR_WIDTH", name), cfg.def->dbits[hw_wr_wide_log2]); + if (pdef.kind != PortKind::Sw) + cell->setParam(stringf("\\PORT_%s_RD_WIDTH", name), cfg.def->dbits[hw_rd_wide_log2]); + } + } + } + + // Address determination. + SigSpec hw_addr; + for (int x: hw_addr_swizzle) { + if (x == -1 || x >= GetSize(addr)) + hw_addr.append(State::S0); + else + hw_addr.append(addr[x]); + } + for (int i = 0; i < hw_wr_wide_log2 && i < hw_rd_wide_log2; i++) + hw_addr[i] = State::S0; + for (auto cell: cells) + cell->setPort(stringf("\\PORT_%s_ADDR", name), hw_addr); + + // Write part. + if (pdef.kind != PortKind::Ar && pdef.kind != PortKind::Sr) { + int width = cfg.def->dbits[hw_wr_wide_log2]; + int effective_byte = cfg.def->byte; + if (effective_byte == 0 || effective_byte > width) + effective_byte = width; + if (wpidx != -1) { + auto &wport = mem.wr_ports[wpidx]; + Swizzle port_swz = gen_swizzle(mem, cfg, wport.wide_log2, hw_wr_wide_log2); + std::vector<SigSpec> big_wren = generate_demux(mem, wpidx, port_swz); + for (int rd = 0; rd < cfg.repl_d; rd++) { + auto cell = cells[rd]; + SigSpec hw_wdata; + SigSpec hw_wren; + for (auto &bit : port_swz.bits[rd]) { + if (!bit.valid) { + hw_wdata.append(State::Sx); + } else { + hw_wdata.append(wport.data[bit.bit]); + } + } + for (int i = 0; i < GetSize(port_swz.bits[rd]); i += effective_byte) { + auto &bit = port_swz.bits[rd][i]; + if (!bit.valid) { + hw_wren.append(State::S0); + } else { + hw_wren.append(big_wren[bit.mux_idx][bit.bit]); + } + } + cell->setPort(stringf("\\PORT_%s_WR_DATA", name), hw_wdata); + if (pdef.wrbe_separate) { + // TODO make some use of it + SigSpec en = mem.module->ReduceOr(NEW_ID, hw_wren); + cell->setPort(stringf("\\PORT_%s_WR_EN", name), en); + cell->setPort(stringf("\\PORT_%s_WR_BE", name), hw_wren); + if (cfg.def->width_mode != WidthMode::Single) + cell->setParam(stringf("\\PORT_%s_WR_BE_WIDTH", name), GetSize(hw_wren)); + } else { + cell->setPort(stringf("\\PORT_%s_WR_EN", name), hw_wren); + if (cfg.def->byte != 0 && cfg.def->width_mode != WidthMode::Single) + cell->setParam(stringf("\\PORT_%s_WR_EN_WIDTH", name), GetSize(hw_wren)); + } + } + } else { + for (auto cell: cells) { + cell->setPort(stringf("\\PORT_%s_WR_DATA", name), Const(State::Sx, width)); + SigSpec hw_wren = Const(State::S0, width / effective_byte); + if (pdef.wrbe_separate) { + cell->setPort(stringf("\\PORT_%s_WR_EN", name), State::S0); + cell->setPort(stringf("\\PORT_%s_WR_BE", name), hw_wren); + cell->setParam(stringf("\\PORT_%s_WR_BE_WIDTH", name), GetSize(hw_wren)); + } else { + cell->setPort(stringf("\\PORT_%s_WR_EN", name), hw_wren); + if (cfg.def->byte != 0) + cell->setParam(stringf("\\PORT_%s_WR_EN_WIDTH", name), GetSize(hw_wren)); + } + } + } + } + + // Read part. + if (pdef.kind != PortKind::Sw) { + int width = cfg.def->dbits[hw_rd_wide_log2]; + if (rpidx != -1) { + auto &rport = mem.rd_ports[rpidx]; + auto &rpcfg = cfg.rd_ports[rpidx]; + Swizzle port_swz = gen_swizzle(mem, cfg, rport.wide_log2, hw_rd_wide_log2); + std::vector<SigSpec> big_rdata = generate_mux(mem, rpidx, port_swz); + for (int rd = 0; rd < cfg.repl_d; rd++) { + auto cell = cells[rd]; + if (pdef.kind == PortKind::Sr || pdef.kind == PortKind::Srsw) { + if (pdef.rd_en) + cell->setPort(stringf("\\PORT_%s_RD_EN", name), rpcfg.rd_en_to_clk_en ? State::S1 : rport.en); + if (pdef.rdarstval != ResetValKind::None) + cell->setPort(stringf("\\PORT_%s_RD_ARST", name), rport.arst); + if (pdef.rdsrstval != ResetValKind::None) + cell->setPort(stringf("\\PORT_%s_RD_SRST", name), rport.srst); + if (pdef.rdinitval == ResetValKind::Any || pdef.rdinitval == ResetValKind::NoUndef) { + Const val = rport.init_value; + if (pdef.rdarstval == ResetValKind::Init && rport.arst != State::S0) { + log_assert(val.is_fully_undef() || val == rport.arst_value); + val = rport.arst_value; + } + if (pdef.rdsrstval == ResetValKind::Init && rport.srst != State::S0) { + log_assert(val.is_fully_undef() || val == rport.srst_value); + val = rport.srst_value; + } + std::vector<State> hw_val; + for (auto &bit : port_swz.bits[rd]) { + if (!bit.valid) { + hw_val.push_back(State::Sx); + } else { + hw_val.push_back(val.bits[bit.bit]); + } + } + if (pdef.rdinitval == ResetValKind::NoUndef) + clean_undef(hw_val); + cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), hw_val); + } + if (pdef.rdarstval == ResetValKind::Any || pdef.rdarstval == ResetValKind::NoUndef) { + std::vector<State> hw_val; + for (auto &bit : port_swz.bits[rd]) { + if (!bit.valid) { + hw_val.push_back(State::Sx); + } else { + hw_val.push_back(rport.arst_value.bits[bit.bit]); + } + } + if (pdef.rdarstval == ResetValKind::NoUndef) + clean_undef(hw_val); + cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), hw_val); + } + if (pdef.rdsrstval == ResetValKind::Any || pdef.rdsrstval == ResetValKind::NoUndef) { + std::vector<State> hw_val; + for (auto &bit : port_swz.bits[rd]) { + if (!bit.valid) { + hw_val.push_back(State::Sx); + } else { + hw_val.push_back(rport.srst_value.bits[bit.bit]); + } + } + if (pdef.rdsrstval == ResetValKind::NoUndef) + clean_undef(hw_val); + cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), hw_val); + } + } + SigSpec hw_rdata = mem.module->addWire(NEW_ID, width); + cell->setPort(stringf("\\PORT_%s_RD_DATA", name), hw_rdata); + SigSpec lhs; + SigSpec rhs; + for (int i = 0; i < GetSize(hw_rdata); i++) { + auto &bit = port_swz.bits[rd][i]; + if (bit.valid) { + lhs.append(big_rdata[bit.mux_idx][bit.bit]); + rhs.append(hw_rdata[i]); + } + } + mem.module->connect(lhs, rhs); + } + } else { + for (auto cell: cells) { + if (pdef.kind == PortKind::Sr || pdef.kind == PortKind::Srsw) { + if (pdef.rd_en) + cell->setPort(stringf("\\PORT_%s_RD_EN", name), State::S0); + if (pdef.rdarstval != ResetValKind::None) + cell->setPort(stringf("\\PORT_%s_RD_ARST", name), State::S0); + if (pdef.rdsrstval != ResetValKind::None) + cell->setPort(stringf("\\PORT_%s_RD_SRST", name), State::S0); + if (pdef.rdinitval == ResetValKind::Any) + cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), Const(State::Sx, width)); + else if (pdef.rdinitval == ResetValKind::NoUndef) + cell->setParam(stringf("\\PORT_%s_RD_INIT_VALUE", name), Const(State::S0, width)); + if (pdef.rdarstval == ResetValKind::Any) + cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), Const(State::Sx, width)); + else if (pdef.rdarstval == ResetValKind::NoUndef) + cell->setParam(stringf("\\PORT_%s_RD_ARST_VALUE", name), Const(State::S0, width)); + if (pdef.rdsrstval == ResetValKind::Any) + cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), Const(State::Sx, width)); + else if (pdef.rdsrstval == ResetValKind::NoUndef) + cell->setParam(stringf("\\PORT_%s_RD_SRST_VALUE", name), Const(State::S0, width)); + } + SigSpec hw_rdata = mem.module->addWire(NEW_ID, width); + cell->setPort(stringf("\\PORT_%s_RD_DATA", name), hw_rdata); + } + } + } +} + +void MemMapping::emit(const MemConfig &cfg) { + log("mapping memory %s.%s via %s\n", log_id(mem.module->name), log_id(mem.memid), log_id(cfg.def->id)); + // First, handle emulations. + if (cfg.emu_read_first) + mem.emulate_read_first(&worker.initvals); + for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { + auto &pcfg = cfg.rd_ports[pidx]; + auto &port = mem.rd_ports[pidx]; + if (pcfg.emu_sync) + mem.extract_rdff(pidx, &worker.initvals); + else if (pcfg.emu_en) + mem.emulate_rden(pidx, &worker.initvals); + else { + if (pcfg.emu_srst_en_prio) { + if (port.ce_over_srst) + mem.emulate_rd_ce_over_srst(pidx); + else + mem.emulate_rd_srst_over_ce(pidx); + } + mem.emulate_reset(pidx, pcfg.emu_init, pcfg.emu_arst, pcfg.emu_srst, &worker.initvals); + } + } + for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++) { + auto &pcfg = cfg.wr_ports[pidx]; + for (int opidx: pcfg.emu_prio) { + mem.emulate_priority(opidx, pidx, &worker.initvals); + } + } + for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { + auto &port = mem.rd_ports[pidx]; + auto &pcfg = cfg.rd_ports[pidx]; + for (int opidx: pcfg.emu_trans) { + // The port may no longer be transparent due to transparency being + // nuked as part of emu_sync or emu_prio. + if (port.transparency_mask[opidx]) + mem.emulate_transparency(opidx, pidx, &worker.initvals); + } + } + + // tgt (repl, port group, port) -> mem (wr port, rd port), where -1 means no port. + std::vector<std::vector<std::vector<std::pair<int, int>>>> ports(cfg.repl_port); + for (int i = 0; i < cfg.repl_port; i++) + ports[i].resize(cfg.def->port_groups.size()); + for (int i = 0; i < GetSize(cfg.wr_ports); i++) { + auto &pcfg = cfg.wr_ports[i]; + for (int j = 0; j < cfg.repl_port; j++) { + if (j == 0) { + ports[j][pcfg.port_group].push_back({i, pcfg.rd_port}); + } else { + ports[j][pcfg.port_group].push_back({i, -1}); + } + } + } + for (int i = 0; i < GetSize(cfg.rd_ports); i++) { + auto &pcfg = cfg.rd_ports[i]; + if (pcfg.wr_port != -1) + continue; + auto &pg = cfg.def->port_groups[pcfg.port_group]; + int j = 0; + while (GetSize(ports[j][pcfg.port_group]) >= GetSize(pg.names)) + j++; + ports[j][pcfg.port_group].push_back({-1, i}); + } + + Swizzle init_swz = gen_swizzle(mem, cfg, 0, GetSize(cfg.def->dbits) - 1); + Const init_data = mem.get_init_data(); + + std::vector<int> hw_addr_swizzle; + for (int i = 0; i < cfg.base_width_log2; i++) + hw_addr_swizzle.push_back(-1); + for (int i = 0; i < init_swz.addr_shift; i++) + if (!(cfg.emu_wide_mask & 1 << i)) + hw_addr_swizzle.push_back(i); + log_assert(GetSize(hw_addr_swizzle) == cfg.def->abits); + + for (int rp = 0; rp < cfg.repl_port; rp++) { + std::vector<Cell *> cells; + for (int rd = 0; rd < cfg.repl_d; rd++) { + Cell *cell = mem.module->addCell(stringf("%s.%d.%d", mem.memid.c_str(), rp, rd), cfg.def->id); + if (cfg.def->width_mode == WidthMode::Global) + cell->setParam(ID::WIDTH, cfg.def->dbits[cfg.base_width_log2]); + if (cfg.def->widthscale) { + std::vector<State> val; + for (auto &bit: init_swz.bits[rd]) + val.push_back(bit.valid ? State::S1 : State::S0); + cell->setParam(ID::BITS_USED, val); + } + for (auto &it: cfg.def->options) + cell->setParam(stringf("\\OPTION_%s", it.first.c_str()), it.second); + for (int i = 0; i < GetSize(cfg.def->shared_clocks); i++) { + auto &cdef = cfg.def->shared_clocks[i]; + auto &ccfg = cfg.shared_clocks[i]; + if (cdef.anyedge) { + cell->setParam(stringf("\\CLK_%s_POL", cdef.name.c_str()), ccfg.used ? ccfg.polarity : true); + cell->setPort(stringf("\\CLK_%s", cdef.name.c_str()), ccfg.used ? ccfg.clk : State::S0); + } else { + SigSpec sig = ccfg.used ? ccfg.clk : State::S0; + if (ccfg.used && ccfg.invert) + sig = mem.module->Not(NEW_ID, sig); + cell->setPort(stringf("\\CLK_%s", cdef.name.c_str()), sig); + } + } + if (cfg.def->init == MemoryInitKind::Any || cfg.def->init == MemoryInitKind::NoUndef) { + std::vector<State> initval; + for (int hwa = 0; hwa < (1 << cfg.def->abits); hwa += 1 << (GetSize(cfg.def->dbits) - 1)) { + for (auto &bit: init_swz.bits[rd]) { + if (!bit.valid) { + initval.push_back(State::Sx); + } else { + int addr = bit.addr; + for (int i = GetSize(cfg.def->dbits) - 1; i < cfg.def->abits; i++) + if (hwa & 1 << i) + addr += 1 << hw_addr_swizzle[i]; + if (addr >= mem.start_offset && addr < mem.start_offset + mem.size) + initval.push_back(init_data.bits[(addr - mem.start_offset) * mem.width + bit.bit]); + else + initval.push_back(State::Sx); + } + } + } + if (cfg.def->init == MemoryInitKind::NoUndef) + clean_undef(initval); + cell->setParam(ID::INIT, initval); + } + cells.push_back(cell); + } + for (int pgi = 0; pgi < GetSize(cfg.def->port_groups); pgi++) { + auto &pg = cfg.def->port_groups[pgi]; + for (int pi = 0; pi < GetSize(pg.names); pi++) { + bool used = pi < GetSize(ports[rp][pgi]); + bool used_r = false; + bool used_w = false; + if (used) { + auto &pd = ports[rp][pgi][pi]; + const PortVariant *pdef; + if (pd.first != -1) + pdef = cfg.wr_ports[pd.first].def; + else + pdef = cfg.rd_ports[pd.second].def; + used_w = pd.first != -1; + used_r = pd.second != -1; + emit_port(cfg, cells, *pdef, pg.names[pi].c_str(), pd.first, pd.second, hw_addr_swizzle); + } else { + emit_port(cfg, cells, pg.variants[0], pg.names[pi].c_str(), -1, -1, hw_addr_swizzle); + } + if (pg.optional) + for (auto cell: cells) + cell->setParam(stringf("\\PORT_%s_USED", pg.names[pi].c_str()), used); + if (pg.optional_rw) + for (auto cell: cells) { + cell->setParam(stringf("\\PORT_%s_RD_USED", pg.names[pi].c_str()), used_r); + cell->setParam(stringf("\\PORT_%s_WR_USED", pg.names[pi].c_str()), used_w); + } + } + } + } + mem.remove(); +} + +struct MemoryLibMapPass : public Pass { + MemoryLibMapPass() : Pass("memory_libmap", "map memories to cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" memory_libmap -lib <library_file> [-D <condition>] [selection]\n"); + log("\n"); + log("This pass takes a description of available RAM cell types and maps\n"); + log("all selected memories to one of them, or leaves them to be mapped to FFs.\n"); + log("\n"); + log(" -lib <library_file>\n"); + log(" Selects a library file containing RAM cell definitions. This option\n"); + log(" can be passed more than once to select multiple libraries.\n"); + log(" See passes/memory/memlib.md for description of the library format.\n"); + log("\n"); + log(" -D <condition>\n"); + log(" Enables a condition that can be checked within the library file\n"); + log(" to eg. select between slightly different hardware variants.\n"); + log(" This option can be passed any number of times.\n"); + log("\n"); + log(" -logic-cost-rom <num>\n"); + log(" -logic-cost-ram <num>\n"); + log(" Sets the cost of a single bit for memory lowered to soft logic.\n"); + log("\n"); + log(" -no-auto-distributed\n"); + log(" -no-auto-block\n"); + log(" -no-auto-huge\n"); + log(" Disables automatic mapping of given kind of RAMs. Manual mapping\n"); + log(" (using ram_style or other attributes) is still supported.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + std::vector<std::string> lib_files; + pool<std::string> defines; + PassOptions opts; + opts.no_auto_distributed = false; + opts.no_auto_block = false; + opts.no_auto_huge = false; + opts.logic_cost_ram = 1.0; + opts.logic_cost_rom = 1.0/16.0; + log_header(design, "Executing MEMORY_LIBMAP pass (mapping memories to cells).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-lib" && argidx+1 < args.size()) { + lib_files.push_back(args[++argidx]); + continue; + } + if (args[argidx] == "-D" && argidx+1 < args.size()) { + defines.insert(args[++argidx]); + continue; + } + if (args[argidx] == "-no-auto-distributed") { + opts.no_auto_distributed = true; + continue; + } + if (args[argidx] == "-no-auto-block") { + opts.no_auto_block = true; + continue; + } + if (args[argidx] == "-no-auto-huge") { + opts.no_auto_huge = true; + continue; + } + if (args[argidx] == "-logic-cost-rom" && argidx+1 < args.size()) { + opts.logic_cost_rom = strtod(args[++argidx].c_str(), nullptr); + continue; + } + if (args[argidx] == "-logic-cost-ram" && argidx+1 < args.size()) { + opts.logic_cost_ram = strtod(args[++argidx].c_str(), nullptr); + continue; + } + break; + } + extra_args(args, argidx, design); + + Library lib = parse_library(lib_files, defines); + + for (auto module : design->selected_modules()) { + MapWorker worker(module); + auto mems = Mem::get_selected_memories(module); + for (auto &mem : mems) + { + MemMapping map(worker, mem, lib, opts); + int idx = -1; + int best = map.logic_cost; + if (!map.logic_ok) { + if (map.cfgs.empty()) + log_error("no valid mapping found for memory %s.%s\n", log_id(module->name), log_id(mem.memid)); + idx = 0; + best = map.cfgs[0].cost; + } + for (int i = 0; i < GetSize(map.cfgs); i++) { + if (map.cfgs[i].cost < best) { + idx = i; + best = map.cfgs[i].cost; + } + } + if (idx == -1) { + log("using FF mapping for memory %s.%s\n", log_id(module->name), log_id(mem.memid)); + } else { + map.emit(map.cfgs[idx]); + } + } + } + } +} MemoryLibMapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/memory/memory_share.cc b/passes/memory/memory_share.cc index 9d82739aa..5cb11d62b 100644 --- a/passes/memory/memory_share.cc +++ b/passes/memory/memory_share.cc @@ -82,6 +82,11 @@ struct MemoryShareWorker log("Consolidating read ports of memory %s.%s by address:\n", log_id(module), log_id(mem.memid)); bool changed = false; + int abits = 0; + for (auto &port: mem.rd_ports) { + if (GetSize(port.addr) > abits) + abits = GetSize(port.addr); + } for (int i = 0; i < GetSize(mem.rd_ports); i++) { auto &port1 = mem.rd_ports[i]; @@ -114,6 +119,8 @@ struct MemoryShareWorker int wide_log2 = std::max(port1.wide_log2, port2.wide_log2); SigSpec addr1 = sigmap_xmux(port1.addr); SigSpec addr2 = sigmap_xmux(port2.addr); + addr1.extend_u0(abits); + addr2.extend_u0(abits); if (GetSize(addr1) <= wide_log2) continue; if (GetSize(addr2) <= wide_log2) @@ -192,6 +199,11 @@ struct MemoryShareWorker log("Consolidating write ports of memory %s.%s by address:\n", log_id(module), log_id(mem.memid)); bool changed = false; + int abits = 0; + for (auto &port: mem.wr_ports) { + if (GetSize(port.addr) > abits) + abits = GetSize(port.addr); + } for (int i = 0; i < GetSize(mem.wr_ports); i++) { auto &port1 = mem.wr_ports[i]; @@ -216,6 +228,8 @@ struct MemoryShareWorker int wide_log2 = std::max(port1.wide_log2, port2.wide_log2); SigSpec addr1 = sigmap_xmux(port1.addr); SigSpec addr2 = sigmap_xmux(port2.addr); + addr1.extend_u0(abits); + addr2.extend_u0(abits); if (GetSize(addr1) <= wide_log2) continue; if (GetSize(addr2) <= wide_log2) @@ -416,7 +430,9 @@ struct MemoryShareWorker else this_addr.extend_u0(GetSize(last_addr)); - port1.addr = module->Mux(NEW_ID, last_addr, this_addr, this_en_active); + SigSpec new_addr = module->Mux(NEW_ID, last_addr.extract_end(port1.wide_log2), this_addr.extract_end(port1.wide_log2), this_en_active); + + port1.addr = SigSpec({new_addr, port1.addr.extract(0, port1.wide_log2)}); port1.data = module->Mux(NEW_ID, last_data, this_data, this_en_active); std::map<std::pair<RTLIL::SigBit, RTLIL::SigBit>, int> groups_en; @@ -539,7 +555,7 @@ struct MemorySharePass : public Pass { } break; } - extra_args(args, 1, design); + extra_args(args, argidx, design); MemoryShareWorker msw(design, flag_widen, flag_sat); for (auto module : design->selected_modules()) diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 4e52ad8da..76bf8a84e 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -19,6 +19,7 @@ OBJS += passes/opt/opt_demorgan.o OBJS += passes/opt/rmports.o OBJS += passes/opt/opt_lut.o OBJS += passes/opt/opt_lut_ins.o +OBJS += passes/opt/opt_ffinv.o OBJS += passes/opt/pmux2shiftx.o OBJS += passes/opt/muxpack.o endif diff --git a/passes/opt/opt.cc b/passes/opt/opt.cc index c3e418c07..dc88563c2 100644 --- a/passes/opt/opt.cc +++ b/passes/opt/opt.cc @@ -114,6 +114,7 @@ struct OptPass : public Pass { if (args[argidx] == "-keepdc") { opt_expr_args += " -keepdc"; opt_dff_args += " -keepdc"; + opt_merge_args += " -keepdc"; continue; } if (args[argidx] == "-nodffe") { diff --git a/passes/opt/opt_dff.cc b/passes/opt/opt_dff.cc index ddf08392b..0ad4acec2 100644 --- a/passes/opt/opt_dff.cc +++ b/passes/opt/opt_dff.cc @@ -58,13 +58,10 @@ struct OptDffWorker typedef std::pair<RTLIL::SigBit, bool> ctrl_t; typedef std::set<ctrl_t> ctrls_t; - ModWalker modwalker; - QuickConeSat qcsat; - // Used as a queue. std::vector<Cell *> dff_cells; - OptDffWorker(const OptDffOptions &opt, Module *mod) : opt(opt), module(mod), sigmap(mod), initvals(&sigmap, mod), modwalker(module->design, module), qcsat(modwalker) { + OptDffWorker(const OptDffOptions &opt, Module *mod) : opt(opt), module(mod), sigmap(mod), initvals(&sigmap, mod) { // Gathering two kinds of information here for every sigmapped SigBit: // // - bitusers: how many users it has (muxes will only be merged into FFs if this is 1, making the FF the only user) @@ -275,7 +272,7 @@ struct OptDffWorker bool changed = false; if (!ff.width) { - module->remove(cell); + ff.remove(); did_something = true; continue; } @@ -316,6 +313,7 @@ struct OptDffWorker continue; } ff = ff.slice(keep_bits); + ff.cell = cell; changed = true; } @@ -382,6 +380,68 @@ struct OptDffWorker } } + if (ff.has_aload) { + if (ff.sig_aload == (ff.pol_aload ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_aload == State::Sx)) { + // Always-inactive enable — remove. + log("Removing never-active async load on %s (%s) from module %s.\n", + log_id(cell), log_id(cell->type), log_id(module)); + ff.has_aload = false; + changed = true; + } else if (ff.sig_aload == (ff.pol_aload ? State::S1 : State::S0)) { + // Always-active enable. Make a comb circuit, nuke the FF/latch. + log("Handling always-active async load on %s (%s) from module %s (changing to combinatorial circuit).\n", + log_id(cell), log_id(cell->type), log_id(module)); + ff.remove(); + if (ff.has_sr) { + SigSpec tmp; + if (ff.is_fine) { + if (ff.pol_set) + tmp = module->MuxGate(NEW_ID, ff.sig_ad, State::S1, ff.sig_set); + else + tmp = module->MuxGate(NEW_ID, State::S1, ff.sig_ad, ff.sig_set); + if (ff.pol_clr) + module->addMuxGate(NEW_ID, tmp, State::S0, ff.sig_clr, ff.sig_q); + else + module->addMuxGate(NEW_ID, State::S0, tmp, ff.sig_clr, ff.sig_q); + } else { + if (ff.pol_set) + tmp = module->Or(NEW_ID, ff.sig_ad, ff.sig_set); + else + tmp = module->Or(NEW_ID, ff.sig_ad, module->Not(NEW_ID, ff.sig_set)); + if (ff.pol_clr) + module->addAnd(NEW_ID, tmp, module->Not(NEW_ID, ff.sig_clr), ff.sig_q); + else + module->addAnd(NEW_ID, tmp, ff.sig_clr, ff.sig_q); + } + } else if (ff.has_arst) { + if (ff.is_fine) { + if (ff.pol_arst) + module->addMuxGate(NEW_ID, ff.sig_ad, ff.val_arst[0], ff.sig_arst, ff.sig_q); + else + module->addMuxGate(NEW_ID, ff.val_arst[0], ff.sig_ad, ff.sig_arst, ff.sig_q); + } else { + if (ff.pol_arst) + module->addMux(NEW_ID, ff.sig_ad, ff.val_arst, ff.sig_arst, ff.sig_q); + else + module->addMux(NEW_ID, ff.val_arst, ff.sig_ad, ff.sig_arst, ff.sig_q); + } + } else { + module->connect(ff.sig_q, ff.sig_ad); + } + did_something = true; + continue; + } else if (ff.sig_ad.is_fully_const() && !ff.has_arst && !ff.has_sr) { + log("Changing const-value async load to async reset on %s (%s) from module %s.\n", + log_id(cell), log_id(cell->type), log_id(module)); + ff.has_arst = true; + ff.has_aload = false; + ff.sig_arst = ff.sig_aload; + ff.pol_arst = ff.pol_aload; + ff.val_arst = ff.sig_ad.as_const(); + changed = true; + } + } + if (ff.has_arst) { if (ff.sig_arst == (ff.pol_arst ? State::S0 : State::S1)) { // Always-inactive reset — remove. @@ -393,8 +453,7 @@ struct OptDffWorker // Always-active async reset — change to const driver. log("Handling always-active ARST on %s (%s) from module %s (changing to const driver).\n", log_id(cell), log_id(cell->type), log_id(module)); - initvals.remove_init(ff.sig_q); - module->remove(cell); + ff.remove(); module->connect(ff.sig_q, ff.val_arst); did_something = true; continue; @@ -414,111 +473,63 @@ struct OptDffWorker log_id(cell), log_id(cell->type), log_id(module)); ff.has_srst = false; if (!ff.ce_over_srst) - ff.has_en = false; - ff.sig_d = ff.val_d = ff.val_srst; - ff.d_is_const = true; + ff.has_ce = false; + ff.sig_d = ff.val_srst; changed = true; } } - if (ff.has_en) { - if (ff.sig_en == (ff.pol_en ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_en == State::Sx)) { + if (ff.has_ce) { + if (ff.sig_ce == (ff.pol_ce ? State::S0 : State::S1) || (!opt.keepdc && ff.sig_ce == State::Sx)) { // Always-inactive enable — remove. - if (ff.has_clk && ff.has_srst && !ff.ce_over_srst) { + if (ff.has_srst && !ff.ce_over_srst) { log("Handling never-active EN on %s (%s) from module %s (connecting SRST instead).\n", log_id(cell), log_id(cell->type), log_id(module)); // FF with sync reset — connect the sync reset to D instead. - ff.pol_en = ff.pol_srst; - ff.sig_en = ff.sig_srst; + ff.pol_ce = ff.pol_srst; + ff.sig_ce = ff.sig_srst; ff.has_srst = false; - ff.sig_d = ff.val_d = ff.val_srst; - ff.d_is_const = true; + ff.sig_d = ff.val_srst; changed = true; } else { log("Handling never-active EN on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); - // The D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver). - ff.has_d = ff.has_en = ff.has_clk = false; + // The D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver). + ff.has_ce = ff.has_clk = ff.has_srst = false; changed = true; } - } else if (ff.sig_en == (ff.pol_en ? State::S1 : State::S0)) { - // Always-active enable. - if (ff.has_clk) { - // For FF, just remove the useless enable. - log("Removing always-active EN on %s (%s) from module %s.\n", - log_id(cell), log_id(cell->type), log_id(module)); - ff.has_en = false; - changed = true; - } else { - // For latches, make a comb circuit, nuke the latch. - log("Handling always-active EN on %s (%s) from module %s (changing to combinatorial circuit).\n", - log_id(cell), log_id(cell->type), log_id(module)); - initvals.remove_init(ff.sig_q); - module->remove(cell); - if (ff.has_sr) { - SigSpec tmp; - if (ff.is_fine) { - if (ff.pol_set) - tmp = module->MuxGate(NEW_ID, ff.sig_d, State::S1, ff.sig_set); - else - tmp = module->MuxGate(NEW_ID, State::S1, ff.sig_d, ff.sig_set); - if (ff.pol_clr) - module->addMuxGate(NEW_ID, tmp, State::S0, ff.sig_clr, ff.sig_q); - else - module->addMuxGate(NEW_ID, State::S0, tmp, ff.sig_clr, ff.sig_q); - } else { - if (ff.pol_set) - tmp = module->Or(NEW_ID, ff.sig_d, ff.sig_set); - else - tmp = module->Or(NEW_ID, ff.sig_d, module->Not(NEW_ID, ff.sig_set)); - if (ff.pol_clr) - module->addAnd(NEW_ID, tmp, module->Not(NEW_ID, ff.sig_clr), ff.sig_q); - else - module->addAnd(NEW_ID, tmp, ff.sig_clr, ff.sig_q); - } - } else if (ff.has_arst) { - if (ff.is_fine) { - if (ff.pol_arst) - module->addMuxGate(NEW_ID, ff.sig_d, ff.val_arst[0], ff.sig_arst, ff.sig_q); - else - module->addMuxGate(NEW_ID, ff.val_arst[0], ff.sig_d, ff.sig_arst, ff.sig_q); - } else { - if (ff.pol_arst) - module->addMux(NEW_ID, ff.sig_d, ff.val_arst, ff.sig_arst, ff.sig_q); - else - module->addMux(NEW_ID, ff.val_arst, ff.sig_d, ff.sig_arst, ff.sig_q); - } - } else { - module->connect(ff.sig_q, ff.sig_d); - } - did_something = true; - continue; - } + } else if (ff.sig_ce == (ff.pol_ce ? State::S1 : State::S0)) { + // Always-active enable. Just remove it. + // For FF, just remove the useless enable. + log("Removing always-active EN on %s (%s) from module %s.\n", + log_id(cell), log_id(cell->type), log_id(module)); + ff.has_ce = false; + changed = true; } } if (ff.has_clk) { if (ff.sig_clk.is_fully_const()) { - // Const clock — the D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver). + // Const clock — the D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver). log("Handling const CLK on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); - ff.has_d = ff.has_en = ff.has_clk = ff.has_srst = false; + ff.has_ce = ff.has_clk = ff.has_srst = false; changed = true; } } - if (ff.has_d && ff.sig_d == ff.sig_q) { + if ((ff.has_clk || ff.has_gclk) && ff.sig_d == ff.sig_q) { // Q wrapped back to D, can be removed. if (ff.has_clk && ff.has_srst) { // FF with sync reset — connect the sync reset to D instead. log("Handling D = Q on %s (%s) from module %s (conecting SRST instead).\n", log_id(cell), log_id(cell->type), log_id(module)); - if (ff.has_en && ff.ce_over_srst) { - if (!ff.pol_en) { + if (ff.has_ce && ff.ce_over_srst) { + if (!ff.pol_ce) { if (ff.is_fine) - ff.sig_en = module->NotGate(NEW_ID, ff.sig_en); + ff.sig_ce = module->NotGate(NEW_ID, ff.sig_ce); else - ff.sig_en = module->Not(NEW_ID, ff.sig_en); + ff.sig_ce = module->Not(NEW_ID, ff.sig_ce); } if (!ff.pol_srst) { if (ff.is_fine) @@ -527,96 +538,37 @@ struct OptDffWorker ff.sig_srst = module->Not(NEW_ID, ff.sig_srst); } if (ff.is_fine) - ff.sig_en = module->AndGate(NEW_ID, ff.sig_en, ff.sig_srst); + ff.sig_ce = module->AndGate(NEW_ID, ff.sig_ce, ff.sig_srst); else - ff.sig_en = module->And(NEW_ID, ff.sig_en, ff.sig_srst); - ff.pol_en = true; + ff.sig_ce = module->And(NEW_ID, ff.sig_ce, ff.sig_srst); + ff.pol_ce = true; } else { - ff.pol_en = ff.pol_srst; - ff.sig_en = ff.sig_srst; + ff.pol_ce = ff.pol_srst; + ff.sig_ce = ff.sig_srst; } - ff.has_en = true; + ff.has_ce = true; ff.has_srst = false; - ff.sig_d = ff.val_d = ff.val_srst; - ff.d_is_const = true; + ff.sig_d = ff.val_srst; changed = true; } else { // The D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver). log("Handling D = Q on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); - ff.has_d = ff.has_en = ff.has_clk = false; + ff.has_gclk = ff.has_clk = ff.has_ce = false; changed = true; } } - // Now check if any bit can be replaced by a constant. - pool<int> removed_sigbits; - for (int i = 0; i < ff.width; i++) { - State val = ff.val_init[i]; - if (ff.has_arst) - val = combine_const(val, ff.val_arst[i]); - if (ff.has_srst) - val = combine_const(val, ff.val_srst[i]); - if (ff.has_sr) { - if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1)) - val = combine_const(val, State::S0); - if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1)) - val = combine_const(val, State::S1); - } - if (val == State::Sm) - continue; - if (ff.has_d) { - if (!ff.sig_d[i].wire) { - val = combine_const(val, ff.sig_d[i].data); - if (val == State::Sm) - continue; - } else { - if (!opt.sat) - continue; - // For each register bit, try to prove that it cannot change from the initial value. If so, remove it - if (!modwalker.has_drivers(ff.sig_d.extract(i))) - continue; - if (val != State::S0 && val != State::S1) - continue; - - int init_sat_pi = qcsat.importSigBit(val); - int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); - int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]); - - qcsat.prepare(); - - // Try to find out whether the register bit can change under some circumstances - bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); - - // If the register bit cannot change, we can replace it with a constant - if (counter_example_found) - continue; - } - } - log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0, - i, log_id(cell), log_id(cell->type), log_id(module)); - - initvals.remove_init(ff.sig_q[i]); - module->connect(ff.sig_q[i], val); - removed_sigbits.insert(i); - } - if (!removed_sigbits.empty()) { - std::vector<int> keep_bits; - for (int i = 0; i < ff.width; i++) - if (!removed_sigbits.count(i)) - keep_bits.push_back(i); - if (keep_bits.empty()) { - module->remove(cell); - did_something = true; - continue; - } - ff = ff.slice(keep_bits); + if (ff.has_aload && !ff.has_clk && ff.sig_ad == ff.sig_q) { + log("Handling AD = Q on %s (%s) from module %s (removing async load path).\n", + log_id(cell), log_id(cell->type), log_id(module)); + ff.has_aload = false; changed = true; } // The cell has been simplified as much as possible already. Now try to spice it up with enables / sync resets. if (ff.has_clk) { - if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_en || ff.ce_over_srst) && !opt.nosdff) { + if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_ce || ff.ce_over_srst) && !opt.nosdff) { // Try to merge sync resets. std::map<ctrls_t, std::vector<int>> groups; std::vector<int> remaining_indices; @@ -677,9 +629,9 @@ struct OptDffWorker new_ff.has_srst = true; new_ff.sig_srst = srst.first; new_ff.pol_srst = srst.second; - if (new_ff.has_en) + if (new_ff.has_ce) new_ff.ce_over_srst = true; - Cell *new_cell = new_ff.emit(module, NEW_ID); + Cell *new_cell = new_ff.emit(); if (new_cell) dff_cells.push_back(new_cell); log("Adding SRST signal on %s (%s) from module %s (D = %s, Q = %s, rval = %s).\n", @@ -692,10 +644,11 @@ struct OptDffWorker continue; } else if (GetSize(remaining_indices) != ff.width) { ff = ff.slice(remaining_indices); + ff.cell = cell; changed = true; } } - if ((!ff.has_srst || !ff.has_en || !ff.ce_over_srst) && !opt.nodffe) { + if ((!ff.has_srst || !ff.has_ce || !ff.ce_over_srst) && !opt.nodffe) { // Try to merge enables. std::map<std::pair<patterns_t, ctrls_t>, std::vector<int>> groups; std::vector<int> remaining_indices; @@ -725,8 +678,8 @@ struct OptDffWorker if (!opt.simple_dffe) patterns = find_muxtree_feedback_patterns(ff.sig_d[i], ff.sig_q[i], pattern_t()); if (!patterns.empty() || !enables.empty()) { - if (ff.has_en) - enables.insert(ctrl_t(ff.sig_en, ff.pol_en)); + if (ff.has_ce) + enables.insert(ctrl_t(ff.sig_ce, ff.pol_ce)); simplify_patterns(patterns); groups[std::make_pair(patterns, enables)].push_back(i); } else @@ -737,11 +690,11 @@ struct OptDffWorker FfData new_ff = ff.slice(it.second); ctrl_t en = make_patterns_logic(it.first.first, it.first.second, ff.is_fine); - new_ff.has_en = true; - new_ff.sig_en = en.first; - new_ff.pol_en = en.second; + new_ff.has_ce = true; + new_ff.sig_ce = en.first; + new_ff.pol_ce = en.second; new_ff.ce_over_srst = false; - Cell *new_cell = new_ff.emit(module, NEW_ID); + Cell *new_cell = new_ff.emit(); if (new_cell) dff_cells.push_back(new_cell); log("Adding EN signal on %s (%s) from module %s (D = %s, Q = %s).\n", @@ -754,6 +707,7 @@ struct OptDffWorker continue; } else if (GetSize(remaining_indices) != ff.width) { ff = ff.slice(remaining_indices); + ff.cell = cell; changed = true; } } @@ -761,9 +715,116 @@ struct OptDffWorker if (changed) { // Rebuild the FF. - IdString name = cell->name; - module->remove(cell); - ff.emit(module, name); + ff.emit(); + did_something = true; + } + } + return did_something; + } + + bool run_constbits() { + ModWalker modwalker(module->design, module); + QuickConeSat qcsat(modwalker); + + // Run as a separate sub-pass, so that we don't mutate (non-FF) cells under ModWalker. + bool did_something = false; + for (auto cell : module->selected_cells()) { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + FfData ff(&initvals, cell); + + // Now check if any bit can be replaced by a constant. + pool<int> removed_sigbits; + for (int i = 0; i < ff.width; i++) { + State val = ff.val_init[i]; + if (ff.has_arst) + val = combine_const(val, ff.val_arst[i]); + if (ff.has_srst) + val = combine_const(val, ff.val_srst[i]); + if (ff.has_sr) { + if (ff.sig_clr[i] != (ff.pol_clr ? State::S0 : State::S1)) + val = combine_const(val, State::S0); + if (ff.sig_set[i] != (ff.pol_set ? State::S0 : State::S1)) + val = combine_const(val, State::S1); + } + if (val == State::Sm) + continue; + if (ff.has_clk || ff.has_gclk) { + if (!ff.sig_d[i].wire) { + val = combine_const(val, ff.sig_d[i].data); + if (val == State::Sm) + continue; + } else { + if (!opt.sat) + continue; + // For each register bit, try to prove that it cannot change from the initial value. If so, remove it + if (!modwalker.has_drivers(ff.sig_d.extract(i))) + continue; + if (val != State::S0 && val != State::S1) + continue; + + int init_sat_pi = qcsat.importSigBit(val); + int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); + int d_sat_pi = qcsat.importSigBit(ff.sig_d[i]); + + qcsat.prepare(); + + // Try to find out whether the register bit can change under some circumstances + bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); + + // If the register bit cannot change, we can replace it with a constant + if (counter_example_found) + continue; + } + } + if (ff.has_aload) { + if (!ff.sig_ad[i].wire) { + val = combine_const(val, ff.sig_ad[i].data); + if (val == State::Sm) + continue; + } else { + if (!opt.sat) + continue; + // For each register bit, try to prove that it cannot change from the initial value. If so, remove it + if (!modwalker.has_drivers(ff.sig_ad.extract(i))) + continue; + if (val != State::S0 && val != State::S1) + continue; + + int init_sat_pi = qcsat.importSigBit(val); + int q_sat_pi = qcsat.importSigBit(ff.sig_q[i]); + int d_sat_pi = qcsat.importSigBit(ff.sig_ad[i]); + + qcsat.prepare(); + + // Try to find out whether the register bit can change under some circumstances + bool counter_example_found = qcsat.ez->solve(qcsat.ez->IFF(q_sat_pi, init_sat_pi), qcsat.ez->NOT(qcsat.ez->IFF(d_sat_pi, init_sat_pi))); + + // If the register bit cannot change, we can replace it with a constant + if (counter_example_found) + continue; + } + } + log("Setting constant %d-bit at position %d on %s (%s) from module %s.\n", val ? 1 : 0, + i, log_id(cell), log_id(cell->type), log_id(module)); + + initvals.remove_init(ff.sig_q[i]); + module->connect(ff.sig_q[i], val); + removed_sigbits.insert(i); + } + if (!removed_sigbits.empty()) { + std::vector<int> keep_bits; + for (int i = 0; i < ff.width; i++) + if (!removed_sigbits.count(i)) + keep_bits.push_back(i); + if (keep_bits.empty()) { + module->remove(cell); + did_something = true; + continue; + } + ff = ff.slice(keep_bits); + ff.cell = cell; + ff.emit(); did_something = true; } } @@ -845,6 +906,8 @@ struct OptDffPass : public Pass { OptDffWorker worker(opt, mod); if (worker.run()) did_something = true; + if (worker.run_constbits()) + did_something = true; } if (did_something) diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index cdd821c52..be0cd470b 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -441,7 +441,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons if (!noclkinv) { - if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2))) + if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2))) handle_polarity_inv(cell, ID::CLK, ID::CLK_POLARITY, assign_map, invert_map); if (cell->type.in(ID($sr), ID($dffsr), ID($dffsre), ID($dlatchsr))) { @@ -452,10 +452,13 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons if (cell->type.in(ID($adff), ID($adffe), ID($adlatch))) handle_polarity_inv(cell, ID::ARST, ID::ARST_POLARITY, assign_map, invert_map); + if (cell->type.in(ID($aldff), ID($aldffe))) + handle_polarity_inv(cell, ID::ALOAD, ID::ALOAD_POLARITY, assign_map, invert_map); + if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) handle_polarity_inv(cell, ID::SRST, ID::SRST_POLARITY, assign_map, invert_map); - if (cell->type.in(ID($dffe), ID($adffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr))) + if (cell->type.in(ID($dffe), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr))) handle_polarity_inv(cell, ID::EN, ID::EN_POLARITY, assign_map, invert_map); handle_clkpol_celltype_swap(cell, "$_SR_N?_", "$_SR_P?_", ID::S, assign_map, invert_map); @@ -484,6 +487,13 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons handle_clkpol_celltype_swap(cell, "$_SDFFCE_?N??_", "$_SDFFCE_?P??_", ID::R, assign_map, invert_map); handle_clkpol_celltype_swap(cell, "$_SDFFCE_???N_", "$_SDFFCE_???P_", ID::E, assign_map, invert_map); + handle_clkpol_celltype_swap(cell, "$_ALDFF_N?_", "$_ALDFF_P?_", ID::C, assign_map, invert_map); + handle_clkpol_celltype_swap(cell, "$_ALDFF_?N_", "$_ALDFF_?P_", ID::L, assign_map, invert_map); + + handle_clkpol_celltype_swap(cell, "$_ALDFFE_N??_", "$_ALDFFE_P??_", ID::C, assign_map, invert_map); + handle_clkpol_celltype_swap(cell, "$_ALDFFE_?N?_", "$_ALDFFE_?P?_", ID::L, assign_map, invert_map); + handle_clkpol_celltype_swap(cell, "$_ALDFFE_??N_", "$_ALDFFE_??P_", ID::E, assign_map, invert_map); + handle_clkpol_celltype_swap(cell, "$_DFFSR_N??_", "$_DFFSR_P??_", ID::C, assign_map, invert_map); handle_clkpol_celltype_swap(cell, "$_DFFSR_?N?_", "$_DFFSR_?P?_", ID::S, assign_map, invert_map); handle_clkpol_celltype_swap(cell, "$_DFFSR_??N_", "$_DFFSR_??P_", ID::R, assign_map, invert_map); diff --git a/passes/opt/opt_ffinv.cc b/passes/opt/opt_ffinv.cc new file mode 100644 index 000000000..fd76dd2be --- /dev/null +++ b/passes/opt/opt_ffinv.cc @@ -0,0 +1,258 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/modtools.h" +#include "kernel/ffinit.h" +#include "kernel/ff.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct OptFfInvWorker +{ + int count = 0; + RTLIL::Module *module; + ModIndex index; + FfInitVals initvals; + + // Case 1: + // - FF is driven by inverter + // - ... which has no other users + // - all users of FF are LUTs + bool push_d_inv(FfData &ff) { + if (index.query_is_input(ff.sig_d)) + return false; + if (index.query_is_output(ff.sig_d)) + return false; + auto d_ports = index.query_ports(ff.sig_d); + if (d_ports.size() != 2) + return false; + Cell *d_inv = nullptr; + for (auto &port: d_ports) { + if (port.cell == ff.cell && port.port == ID::D) + continue; + if (port.port != ID::Y) + return false; + if (port.cell->type.in(ID($not), ID($_NOT_))) { + // OK + } else if (port.cell->type.in(ID($lut))) { + if (port.cell->getParam(ID::WIDTH) != 1) + return false; + if (port.cell->getParam(ID::LUT).as_int() != 1) + return false; + } else { + return false; + } + log_assert(d_inv == nullptr); + d_inv = port.cell; + } + + if (index.query_is_output(ff.sig_q)) + return false; + auto q_ports = index.query_ports(ff.sig_q); + pool<Cell *> q_luts; + for (auto &port: q_ports) { + if (port.cell == ff.cell && port.port == ID::Q) + continue; + if (port.port != ID::A) + return false; + if (!port.cell->type.in(ID($not), ID($_NOT_), ID($lut))) + return false; + q_luts.insert(port.cell); + } + + ff.flip_rst_bits({0}); + ff.sig_d = d_inv->getPort(ID::A); + + for (Cell *lut: q_luts) { + if (lut->type == ID($lut)) { + int flip_mask = 0; + SigSpec sig_a = lut->getPort(ID::A); + for (int i = 0; i < GetSize(sig_a); i++) { + if (index.sigmap(sig_a[i]) == index.sigmap(ff.sig_q)) { + flip_mask |= 1 << i; + } + } + Const mask = lut->getParam(ID::LUT); + Const new_mask; + for (int j = 0; j < (1 << GetSize(sig_a)); j++) { + new_mask.bits.push_back(mask.bits[j ^ flip_mask]); + } + if (GetSize(sig_a) == 1 && new_mask.as_int() == 2) { + module->connect(lut->getPort(ID::Y), ff.sig_q); + module->remove(lut); + } else { + lut->setParam(ID::LUT, new_mask); + } + } else { + // it was an inverter + module->connect(lut->getPort(ID::Y), ff.sig_q); + module->remove(lut); + } + } + + ff.emit(); + return true; + } + + // Case 2: + // - FF is driven by LUT + // - ... which has no other users + // - FF has one user + // - ... which is an inverter + bool push_q_inv(FfData &ff) { + if (index.query_is_input(ff.sig_d)) + return false; + if (index.query_is_output(ff.sig_d)) + return false; + + Cell *d_lut = nullptr; + auto d_ports = index.query_ports(ff.sig_d); + if (d_ports.size() != 2) + return false; + for (auto &port: d_ports) { + if (port.cell == ff.cell && port.port == ID::D) + continue; + if (port.port != ID::Y) + return false; + if (!port.cell->type.in(ID($not), ID($_NOT_), ID($lut))) + return false; + log_assert(d_lut == nullptr); + d_lut = port.cell; + } + + if (index.query_is_output(ff.sig_q)) + return false; + auto q_ports = index.query_ports(ff.sig_q); + if (q_ports.size() != 2) + return false; + Cell *q_inv = nullptr; + for (auto &port: q_ports) { + if (port.cell == ff.cell && port.port == ID::Q) + continue; + if (port.port != ID::A) + return false; + if (port.cell->type.in(ID($not), ID($_NOT_))) { + // OK + } else if (port.cell->type.in(ID($lut))) { + if (port.cell->getParam(ID::WIDTH) != 1) + return false; + if (port.cell->getParam(ID::LUT).as_int() != 1) + return false; + } else { + return false; + } + log_assert(q_inv == nullptr); + q_inv = port.cell; + } + + ff.flip_rst_bits({0}); + ff.sig_q = q_inv->getPort(ID::Y); + module->remove(q_inv); + + if (d_lut->type == ID($lut)) { + Const mask = d_lut->getParam(ID::LUT); + Const new_mask; + for (int i = 0; i < GetSize(mask); i++) { + if (mask.bits[i] == State::S0) + new_mask.bits.push_back(State::S1); + else + new_mask.bits.push_back(State::S0); + } + d_lut->setParam(ID::LUT, new_mask); + if (d_lut->getParam(ID::WIDTH) == 1 && new_mask.as_int() == 2) { + module->connect(ff.sig_d, d_lut->getPort(ID::A)); + module->remove(d_lut); + } + } else { + // it was an inverter + module->connect(ff.sig_d, d_lut->getPort(ID::A)); + module->remove(d_lut); + } + + ff.emit(); + return true; + } + + OptFfInvWorker(RTLIL::Module *module) : + module(module), index(module), initvals(&index.sigmap, module) + { + log("Discovering LUTs.\n"); + + for (Cell *cell : module->selected_cells()) { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(&initvals, cell); + if (ff.has_sr) + continue; + if (!ff.has_clk) + continue; + if (ff.has_aload) + continue; + if (ff.width != 1) + continue; + + if (push_d_inv(ff)) { + count++; + } else if (push_q_inv(ff)) { + count++; + } + } + } +}; + +struct OptFfInvPass : public Pass { + OptFfInvPass() : Pass("opt_ffinv", "push inverters through FFs") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" opt_ffinv [selection]\n"); + log("\n"); + log("This pass pushes inverters to the other side of a FF when they can be merged\n"); + log("into LUTs on the other side.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing OPT_FFINV pass (push inverters through FFs).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + break; + } + extra_args(args, argidx, design); + + int total_count = 0; + for (auto module : design->selected_modules()) + { + OptFfInvWorker worker(module); + total_count += worker.count; + } + if (total_count) + design->scratchpad_set_bool("opt.did_something", true); + log("Pushed %d inverters.\n", total_count); + } +} OptFfInvPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/opt/opt_lut_ins.cc b/passes/opt/opt_lut_ins.cc index 99043ef7e..2f7c392b2 100644 --- a/passes/opt/opt_lut_ins.cc +++ b/passes/opt/opt_lut_ins.cc @@ -193,6 +193,12 @@ struct OptLutInsPass : public Pass { swz += extra; } } + if (techname == "gowin") { + // Pad the LUT to 1 input, adding consts from the front. + if (new_inputs.empty()) { + new_inputs.insert(new_inputs.begin(), State::S0); + } + } Const new_lut(0, 1 << GetSize(new_inputs)); for (int i = 0; i < GetSize(new_lut); i++) { int lidx = 0; @@ -209,9 +215,9 @@ struct OptLutInsPass : public Pass { } new_lut[i] = lut[lidx]; } - // For ecp5, do not replace with a const driver — the nextpnr + // For ecp5, and gowin do not replace with a const driver — the nextpnr // packer requires a complete set of LUTs for wide LUT muxes. - if (new_inputs.empty() && techname != "ecp5") { + if (new_inputs.empty() && techname != "ecp5" && techname != "gowin") { // const driver. remove_cells.push_back(cell); module->connect(output, new_lut[0]); diff --git a/passes/opt/opt_mem.cc b/passes/opt/opt_mem.cc index edadf2c7b..885b6f97d 100644 --- a/passes/opt/opt_mem.cc +++ b/passes/opt/opt_mem.cc @@ -20,6 +20,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/mem.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -54,31 +55,160 @@ struct OptMemPass : public Pass { SigMap sigmap(module); FfInitVals initvals(&sigmap, module); for (auto &mem : Mem::get_selected_memories(module)) { + std::vector<bool> always_0(mem.width, true); + std::vector<bool> always_1(mem.width, true); bool changed = false; for (auto &port : mem.wr_ports) { if (port.en.is_fully_zero()) { port.removed = true; changed = true; total_count++; + } else { + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + for (int i = 0; i < mem.width; i++) { + int bit = sub * mem.width + i; + if (port.en[bit] != State::S0) { + if (port.data[bit] != State::Sx && port.data[bit] != State::S0) { + always_0[i] = false; + } + if (port.data[bit] != State::Sx && port.data[bit] != State::S1) { + always_1[i] = false; + } + } else { + if (port.data[bit] != State::Sx) { + port.data[bit] = State::Sx; + changed = true; + total_count++; + } + } + } + } } } - if (changed) { - mem.emit(); + for (auto &init : mem.inits) { + for (int i = 0; i < GetSize(init.data); i++) { + State bit = init.data.bits[i]; + int lane = i % mem.width; + if (bit != State::Sx && bit != State::S0) { + always_0[lane] = false; + } + if (bit != State::Sx && bit != State::S1) { + always_1[lane] = false; + } + } } - - if (mem.wr_ports.empty() && mem.inits.empty()) { - // The whole memory array will contain - // only State::Sx, but the embedded read - // registers could have reset or init values. - // They will probably be optimized away by - // opt_dff later. - for (int i = 0; i < GetSize(mem.rd_ports); i++) { - mem.extract_rdff(i, &initvals); - auto &port = mem.rd_ports[i]; - module->connect(port.data, Const(State::Sx, GetSize(port.data))); + std::vector<int> swizzle; + for (int i = 0; i < mem.width; i++) { + if (!always_0[i] && !always_1[i]) { + swizzle.push_back(i); + continue; } + State bit; + if (!always_0[i]) { + log("%s.%s: removing const-1 lane %d\n", log_id(module->name), log_id(mem.memid), i); + bit = State::S1; + } else if (!always_1[i]) { + log("%s.%s: removing const-0 lane %d\n", log_id(module->name), log_id(mem.memid), i); + bit = State::S0; + } else { + log("%s.%s: removing const-x lane %d\n", log_id(module->name), log_id(mem.memid), i); + bit = State::Sx; + } + // Reconnect read port data. + for (auto &port: mem.rd_ports) { + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + int bidx = sub * mem.width + i; + if (!port.clk_enable) { + module->connect(port.data[bidx], bit); + } else { + // The FF will most likely be redundant, but it's up to opt_dff to deal with this. + FfData ff(module, &initvals, NEW_ID); + ff.width = 1; + ff.has_clk = true; + ff.sig_clk = port.clk; + ff.pol_clk = port.clk_polarity; + if (port.en != State::S1) { + ff.has_ce = true; + ff.pol_ce = true; + ff.sig_ce = port.en; + } + if (port.arst != State::S0) { + ff.has_arst = true; + ff.pol_arst = true; + ff.sig_arst = port.arst; + ff.val_arst = port.arst_value[bidx]; + } + if (port.srst != State::S0) { + ff.has_srst = true; + ff.pol_srst = true; + ff.sig_srst = port.srst; + ff.val_srst = port.srst_value[bidx]; + } + ff.sig_d = bit; + ff.sig_q = port.data[bidx]; + ff.val_init = port.init_value[bidx]; + ff.emit(); + } + } + } + } + if (GetSize(swizzle) == 0) { mem.remove(); total_count++; + continue; + } + if (GetSize(swizzle) != mem.width) { + for (auto &port: mem.wr_ports) { + SigSpec new_data; + SigSpec new_en; + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + for (auto i: swizzle) { + new_data.append(port.data[sub * mem.width + i]); + new_en.append(port.en[sub * mem.width + i]); + } + } + port.data = new_data; + port.en = new_en; + } + for (auto &port: mem.rd_ports) { + SigSpec new_data; + Const new_init; + Const new_arst; + Const new_srst; + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + for (auto i: swizzle) { + int bidx = sub * mem.width + i; + new_data.append(port.data[bidx]); + new_init.bits.push_back(port.init_value.bits[bidx]); + new_arst.bits.push_back(port.arst_value.bits[bidx]); + new_srst.bits.push_back(port.srst_value.bits[bidx]); + } + } + port.data = new_data; + port.init_value = new_init; + port.arst_value = new_arst; + port.srst_value = new_srst; + } + for (auto &init: mem.inits) { + Const new_data; + Const new_en; + for (int s = 0; s < GetSize(init.data); s += mem.width) { + for (auto i: swizzle) { + new_data.bits.push_back(init.data.bits[s + i]); + } + } + for (auto i: swizzle) { + new_en.bits.push_back(init.en.bits[i]); + } + init.data = new_data; + init.en = new_en; + } + mem.width = GetSize(swizzle); + changed = true; + total_count++; + } + if (changed) { + mem.emit(); } } } diff --git a/passes/opt/opt_mem_priority.cc b/passes/opt/opt_mem_priority.cc index 49ece570b..a9b145bea 100644 --- a/passes/opt/opt_mem_priority.cc +++ b/passes/opt/opt_mem_priority.cc @@ -34,7 +34,7 @@ struct OptMemPriorityPass : public Pass { log(" opt_mem_priority [selection]\n"); log("\n"); log("This pass detects cases where one memory write port has priority over another\n"); - log("even though they can never collide with each other — ie. there can never be\n"); + log("even though they can never collide with each other -- ie. there can never be\n"); log("a situation where a given memory bit is written by both ports at the same\n"); log("time, for example because of always-different addresses, or mutually exclusive\n"); log("enable signals. In such cases, the priority relation is removed.\n"); diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 115eb97a9..e9d98cd43 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -219,7 +219,15 @@ struct OptMergeWorker return conn1 == conn2; } - OptMergeWorker(RTLIL::Design *design, RTLIL::Module *module, bool mode_nomux, bool mode_share_all) : + bool has_dont_care_initval(const RTLIL::Cell *cell) + { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + return false; + + return !initvals(cell->getPort(ID::Q)).is_fully_def(); + } + + OptMergeWorker(RTLIL::Design *design, RTLIL::Module *module, bool mode_nomux, bool mode_share_all, bool mode_keepdc) : design(design), module(module), assign_map(module), mode_share_all(mode_share_all) { total_count = 0; @@ -253,6 +261,8 @@ struct OptMergeWorker for (auto &it : module->cells_) { if (!design->selected(module, it.second)) continue; + if (mode_keepdc && has_dont_care_initval(it.second)) + continue; if (ct.cell_known(it.second->type) || (mode_share_all && it.second->known())) cells.push_back(it.second); } @@ -319,6 +329,9 @@ struct OptMergePass : public Pass { log(" -share_all\n"); log(" Operate on all cell types, not just built-in types.\n"); log("\n"); + log(" -keepdc\n"); + log(" Do not merge flipflops with don't-care bits in their initial value.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { @@ -326,6 +339,7 @@ struct OptMergePass : public Pass { bool mode_nomux = false; bool mode_share_all = false; + bool mode_keepdc = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -338,13 +352,17 @@ struct OptMergePass : public Pass { mode_share_all = true; continue; } + if (arg == "-keepdc") { + mode_keepdc = true; + continue; + } break; } extra_args(args, argidx, design); int total_count = 0; for (auto module : design->selected_modules()) { - OptMergeWorker worker(design, module, mode_nomux, mode_share_all); + OptMergeWorker worker(design, module, mode_nomux, mode_share_all, mode_keepdc); total_count += worker.total_count; } diff --git a/passes/opt/opt_reduce.cc b/passes/opt/opt_reduce.cc index b558f547e..1a7c93fbd 100644 --- a/passes/opt/opt_reduce.cc +++ b/passes/opt/opt_reduce.cc @@ -100,7 +100,7 @@ struct OptReduceWorker return; } - void opt_mux(RTLIL::Cell *cell) + void opt_pmux(RTLIL::Cell *cell) { RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); @@ -141,20 +141,20 @@ struct OptReduceWorker handled_sig.insert(this_b); } - if (new_sig_s.size() != sig_s.size()) { - log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s)); - did_something = true; - total_count++; - } - if (new_sig_s.size() == 0) { - module->connect(RTLIL::SigSig(cell->getPort(ID::Y), cell->getPort(ID::A))); + module->connect(cell->getPort(ID::Y), cell->getPort(ID::A)); assign_map.add(cell->getPort(ID::Y), cell->getPort(ID::A)); module->remove(cell); + did_something = true; + total_count++; + return; } - else - { + + if (new_sig_s.size() != sig_s.size() || (new_sig_s.size() == 1 && cell->type == ID($pmux))) { + log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s)); + did_something = true; + total_count++; cell->setPort(ID::B, new_sig_b); cell->setPort(ID::S, new_sig_s); if (new_sig_s.size() > 1) { @@ -166,81 +166,347 @@ struct OptReduceWorker } } - void opt_mux_bits(RTLIL::Cell *cell) + void opt_bmux(RTLIL::Cell *cell) { - std::vector<RTLIL::SigBit> sig_a = assign_map(cell->getPort(ID::A)).to_sigbit_vector(); - std::vector<RTLIL::SigBit> sig_b = assign_map(cell->getPort(ID::B)).to_sigbit_vector(); - std::vector<RTLIL::SigBit> sig_y = assign_map(cell->getPort(ID::Y)).to_sigbit_vector(); + RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); + RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S)); + int width = cell->getParam(ID::WIDTH).as_int(); + + RTLIL::SigSpec new_sig_a, new_sig_s; + dict<RTLIL::SigBit, int> handled_bits; + + // 0 and up: index of new_sig_s bit + // -1: const 0 + // -2: const 1 + std::vector<int> swizzle; + + for (int i = 0; i < sig_s.size(); i++) + { + SigBit bit = sig_s[i]; + if (bit == State::S0) { + swizzle.push_back(-1); + } else if (bit == State::S1) { + swizzle.push_back(-2); + } else { + auto it = handled_bits.find(bit); + if (it == handled_bits.end()) { + int new_idx = GetSize(new_sig_s); + new_sig_s.append(bit); + handled_bits[bit] = new_idx; + swizzle.push_back(new_idx); + } else { + swizzle.push_back(it->second); + } + } + } + + for (int i = 0; i < (1 << GetSize(new_sig_s)); i++) { + int idx = 0; + for (int j = 0; j < GetSize(sig_s); j++) { + if (swizzle[j] == -1) { + // const 0. + } else if (swizzle[j] == -2) { + // const 1. + idx |= 1 << j; + } else { + if (i & 1 << swizzle[j]) + idx |= 1 << j; + } + } + new_sig_a.append(sig_a.extract(idx * width, width)); + } + + if (new_sig_s.size() == 0) + { + module->connect(cell->getPort(ID::Y), new_sig_a); + assign_map.add(cell->getPort(ID::Y), new_sig_a); + module->remove(cell); + did_something = true; + total_count++; + return; + } + + if (new_sig_s.size() == 1) + { + cell->type = ID($mux); + cell->setPort(ID::A, new_sig_a.extract(0, width)); + cell->setPort(ID::B, new_sig_a.extract(width, width)); + cell->setPort(ID::S, new_sig_s); + cell->parameters.erase(ID::S_WIDTH); + did_something = true; + total_count++; + return; + } + + if (new_sig_s.size() != sig_s.size()) { + log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s)); + did_something = true; + total_count++; + cell->setPort(ID::A, new_sig_a); + cell->setPort(ID::S, new_sig_s); + cell->parameters[ID::S_WIDTH] = RTLIL::Const(new_sig_s.size()); + } + } + + void opt_demux(RTLIL::Cell *cell) + { + RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S)); + int width = cell->getParam(ID::WIDTH).as_int(); + + RTLIL::SigSpec new_sig_y, new_sig_s; + dict<RTLIL::SigBit, int> handled_bits; + + // 0 and up: index of new_sig_s bit + // -1: const 0 + // -2: const 1 + std::vector<int> swizzle; + + for (int i = 0; i < sig_s.size(); i++) + { + SigBit bit = sig_s[i]; + if (bit == State::S0) { + swizzle.push_back(-1); + } else if (bit == State::S1) { + swizzle.push_back(-2); + } else { + auto it = handled_bits.find(bit); + if (it == handled_bits.end()) { + int new_idx = GetSize(new_sig_s); + new_sig_s.append(bit); + handled_bits[bit] = new_idx; + swizzle.push_back(new_idx); + } else { + swizzle.push_back(it->second); + } + } + } + + pool<int> nonzero_idx; + + for (int i = 0; i < (1 << GetSize(new_sig_s)); i++) { + int idx = 0; + for (int j = 0; j < GetSize(sig_s); j++) { + if (swizzle[j] == -1) { + // const 0. + } else if (swizzle[j] == -2) { + // const 1. + idx |= 1 << j; + } else { + if (i & 1 << swizzle[j]) + idx |= 1 << j; + } + } + log_assert(!nonzero_idx.count(idx)); + nonzero_idx.insert(idx); + new_sig_y.append(sig_y.extract(idx * width, width)); + } + + if (new_sig_s.size() == sig_s.size() && sig_s.size() > 0) + return; + + log(" New ctrl vector for %s cell %s: %s\n", cell->type.c_str(), cell->name.c_str(), log_signal(new_sig_s)); + did_something = true; + total_count++; + + for (int i = 0; i < (1 << GetSize(sig_s)); i++) { + if (!nonzero_idx.count(i)) { + SigSpec slice = sig_y.extract(i * width, width); + module->connect(slice, Const(State::S0, width)); + assign_map.add(slice, Const(State::S0, width)); + } + } + + if (new_sig_s.size() == 0) + { + module->connect(new_sig_y, cell->getPort(ID::A)); + assign_map.add(new_sig_y, cell->getPort(ID::A)); + module->remove(cell); + } + else + { + cell->setPort(ID::S, new_sig_s); + cell->setPort(ID::Y, new_sig_y); + cell->parameters[ID::S_WIDTH] = RTLIL::Const(new_sig_s.size()); + } + } + + bool opt_mux_bits(RTLIL::Cell *cell) + { + SigSpec sig_a = assign_map(cell->getPort(ID::A)); + SigSpec sig_b; + SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + int width = GetSize(sig_y); + + if (cell->type != ID($bmux)) + sig_b = assign_map(cell->getPort(ID::B)); - std::vector<RTLIL::SigBit> new_sig_y; RTLIL::SigSig old_sig_conn; - std::vector<std::vector<RTLIL::SigBit>> consolidated_in_tuples; - std::map<std::vector<RTLIL::SigBit>, RTLIL::SigBit> consolidated_in_tuples_map; + dict<SigSpec, SigBit> consolidated_in_tuples; + std::vector<int> swizzle; - for (int i = 0; i < int(sig_y.size()); i++) + for (int i = 0; i < width; i++) { - std::vector<RTLIL::SigBit> in_tuple; + SigSpec in_tuple; bool all_tuple_bits_same = true; - in_tuple.push_back(sig_a.at(i)); - for (int j = i; j < int(sig_b.size()); j += int(sig_a.size())) { - if (sig_b.at(j) != sig_a.at(i)) + in_tuple.append(sig_a[i]); + for (int j = i; j < GetSize(sig_a); j += width) { + in_tuple.append(sig_a[j]); + if (sig_a[j] != in_tuple[0]) + all_tuple_bits_same = false; + } + for (int j = i; j < GetSize(sig_b); j += width) { + in_tuple.append(sig_b[j]); + if (sig_b[j] != in_tuple[0]) all_tuple_bits_same = false; - in_tuple.push_back(sig_b.at(j)); } if (all_tuple_bits_same) { - old_sig_conn.first.append(sig_y.at(i)); - old_sig_conn.second.append(sig_a.at(i)); + old_sig_conn.first.append(sig_y[i]); + old_sig_conn.second.append(sig_a[i]); + continue; } - else if (consolidated_in_tuples_map.count(in_tuple)) + + auto it = consolidated_in_tuples.find(in_tuple); + if (it == consolidated_in_tuples.end()) { - old_sig_conn.first.append(sig_y.at(i)); - old_sig_conn.second.append(consolidated_in_tuples_map.at(in_tuple)); + consolidated_in_tuples[in_tuple] = sig_y[i]; + swizzle.push_back(i); } else { - consolidated_in_tuples_map[in_tuple] = sig_y.at(i); - consolidated_in_tuples.push_back(in_tuple); - new_sig_y.push_back(sig_y.at(i)); + old_sig_conn.first.append(sig_y[i]); + old_sig_conn.second.append(it->second); } } - if (new_sig_y.size() != sig_y.size()) + if (GetSize(swizzle) != width) { log(" Consolidated identical input bits for %s cell %s:\n", cell->type.c_str(), cell->name.c_str()); - log(" Old ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), - log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y))); - - cell->setPort(ID::A, RTLIL::SigSpec()); - for (auto &in_tuple : consolidated_in_tuples) { - RTLIL::SigSpec new_a = cell->getPort(ID::A); - new_a.append(in_tuple.at(0)); - cell->setPort(ID::A, new_a); + if (cell->type != ID($bmux)) { + log(" Old ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y))); + } else { + log(" Old ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::Y))); } - cell->setPort(ID::B, RTLIL::SigSpec()); - for (int i = 1; i <= cell->getPort(ID::S).size(); i++) - for (auto &in_tuple : consolidated_in_tuples) { - RTLIL::SigSpec new_b = cell->getPort(ID::B); - new_b.append(in_tuple.at(i)); - cell->setPort(ID::B, new_b); + if (swizzle.empty()) { + module->remove(cell); + } else { + SigSpec new_sig_a; + for (int i = 0; i < GetSize(sig_a); i += width) + for (int j: swizzle) + new_sig_a.append(sig_a[i+j]); + cell->setPort(ID::A, new_sig_a); + + if (cell->type != ID($bmux)) { + SigSpec new_sig_b; + for (int i = 0; i < GetSize(sig_b); i += width) + for (int j: swizzle) + new_sig_b.append(sig_b[i+j]); + cell->setPort(ID::B, new_sig_b); } - cell->parameters[ID::WIDTH] = RTLIL::Const(new_sig_y.size()); - cell->setPort(ID::Y, new_sig_y); + SigSpec new_sig_y; + for (int j: swizzle) + new_sig_y.append(sig_y[j]); + cell->setPort(ID::Y, new_sig_y); + + cell->parameters[ID::WIDTH] = RTLIL::Const(GetSize(swizzle)); + + if (cell->type != ID($bmux)) { + log(" New ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y))); + } else { + log(" New ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::Y))); + } + } - log(" New ports: A=%s, B=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), - log_signal(cell->getPort(ID::B)), log_signal(cell->getPort(ID::Y))); log(" New connections: %s = %s\n", log_signal(old_sig_conn.first), log_signal(old_sig_conn.second)); + module->connect(old_sig_conn); + + did_something = true; + total_count++; + } + return swizzle.empty(); + } + + bool opt_demux_bits(RTLIL::Cell *cell) { + SigSpec sig_a = assign_map(cell->getPort(ID::A)); + SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + int width = GetSize(sig_a); + + RTLIL::SigSig old_sig_conn; + + dict<SigBit, int> handled_bits; + std::vector<int> swizzle; + + for (int i = 0; i < width; i++) + { + if (sig_a[i] == State::S0) + { + for (int j = i; j < GetSize(sig_y); j += width) + { + old_sig_conn.first.append(sig_y[j]); + old_sig_conn.second.append(State::S0); + } + continue; + } + auto it = handled_bits.find(sig_a[i]); + if (it == handled_bits.end()) + { + handled_bits[sig_a[i]] = i; + swizzle.push_back(i); + } + else + { + for (int j = 0; j < GetSize(sig_y); j += width) + { + old_sig_conn.first.append(sig_y[i+j]); + old_sig_conn.second.append(sig_y[it->second+j]); + } + } + } + + if (GetSize(swizzle) != width) + { + log(" Consolidated identical input bits for %s cell %s:\n", cell->type.c_str(), cell->name.c_str()); + log(" Old ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::Y))); + + if (swizzle.empty()) { + module->remove(cell); + } else { + SigSpec new_sig_a; + for (int j: swizzle) + new_sig_a.append(sig_a[j]); + cell->setPort(ID::A, new_sig_a); + + SigSpec new_sig_y; + for (int i = 0; i < GetSize(sig_y); i += width) + for (int j: swizzle) + new_sig_y.append(sig_y[i+j]); + cell->setPort(ID::Y, new_sig_y); + + cell->parameters[ID::WIDTH] = RTLIL::Const(GetSize(swizzle)); + + log(" New ports: A=%s, Y=%s\n", log_signal(cell->getPort(ID::A)), + log_signal(cell->getPort(ID::Y))); + } + + log(" New connections: %s = %s\n", log_signal(old_sig_conn.first), log_signal(old_sig_conn.second)); module->connect(old_sig_conn); did_something = true; total_count++; } + return swizzle.empty(); } OptReduceWorker(RTLIL::Design *design, RTLIL::Module *module, bool do_fine) : @@ -309,20 +575,31 @@ struct OptReduceWorker // merge identical inputs on $mux and $pmux cells - std::vector<RTLIL::Cell*> cells; - - for (auto &it : module->cells_) - if ((it.second->type == ID($mux) || it.second->type == ID($pmux)) && design->selected(module, it.second)) - cells.push_back(it.second); - - for (auto cell : cells) + for (auto cell : module->selected_cells()) { + if (!cell->type.in(ID($mux), ID($pmux), ID($bmux), ID($demux))) + continue; + // this optimization is to aggressive for most coarse-grain applications. // but we always want it for multiplexers driving write enable ports. - if (do_fine || mem_wren_sigs.check_any(assign_map(cell->getPort(ID::Y)))) - opt_mux_bits(cell); + if (do_fine || mem_wren_sigs.check_any(assign_map(cell->getPort(ID::Y)))) { + if (cell->type == ID($demux)) { + if (opt_demux_bits(cell)) + continue; + } else { + if (opt_mux_bits(cell)) + continue; + } + } + + if (cell->type.in(ID($mux), ID($pmux))) + opt_pmux(cell); + + if (cell->type == ID($bmux)) + opt_bmux(cell); - opt_mux(cell); + if (cell->type == ID($demux)) + opt_demux(cell); } } diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg index 7a01cbd51..4de479122 100644 --- a/passes/pmgen/ice40_dsp.pmg +++ b/passes/pmgen/ice40_dsp.pmg @@ -28,9 +28,8 @@ code sigA sigB sigH for (i = GetSize(sig)-1; i > 0; i--) if (sig[i] != sig[i-1]) break; - // Do not remove non-const sign bit - if (sig[i].wire) - ++i; + // Do not remove sign bit + ++i; return sig.extract(0, i); }; sigA = unextend(port(mul, \A)); diff --git a/passes/proc/Makefile.inc b/passes/proc/Makefile.inc index 50244bf33..21e169a34 100644 --- a/passes/proc/Makefile.inc +++ b/passes/proc/Makefile.inc @@ -5,6 +5,7 @@ OBJS += passes/proc/proc_clean.o OBJS += passes/proc/proc_rmdead.o OBJS += passes/proc/proc_init.o OBJS += passes/proc/proc_arst.o +OBJS += passes/proc/proc_rom.o OBJS += passes/proc/proc_mux.o OBJS += passes/proc/proc_dlatch.o OBJS += passes/proc/proc_dff.o diff --git a/passes/proc/proc.cc b/passes/proc/proc.cc index d7aac57b6..c18651d5e 100644 --- a/passes/proc/proc.cc +++ b/passes/proc/proc.cc @@ -40,6 +40,7 @@ struct ProcPass : public Pass { log(" proc_prune\n"); log(" proc_init\n"); log(" proc_arst\n"); + log(" proc_rom\n"); log(" proc_mux\n"); log(" proc_dlatch\n"); log(" proc_dff\n"); @@ -55,6 +56,9 @@ struct ProcPass : public Pass { log(" -nomux\n"); log(" Will omit the proc_mux pass.\n"); log("\n"); + log(" -norom\n"); + log(" Will omit the proc_rom pass.\n"); + log("\n"); log(" -global_arst [!]<netname>\n"); log(" This option is passed through to proc_arst.\n"); log("\n"); @@ -72,6 +76,7 @@ struct ProcPass : public Pass { bool ifxmode = false; bool nomux = false; bool noopt = false; + bool norom = false; log_header(design, "Executing PROC pass (convert processes to netlists).\n"); log_push(); @@ -95,6 +100,10 @@ struct ProcPass : public Pass { noopt = true; continue; } + if (args[argidx] == "-norom") { + norom = true; + continue; + } break; } extra_args(args, argidx, design); @@ -108,6 +117,8 @@ struct ProcPass : public Pass { Pass::call(design, "proc_arst"); else Pass::call(design, "proc_arst -global_arst " + global_arst); + if (!norom) + Pass::call(design, "proc_rom"); if (!nomux) Pass::call(design, ifxmode ? "proc_mux -ifx" : "proc_mux"); Pass::call(design, "proc_dlatch"); diff --git a/passes/proc/proc_dff.cc b/passes/proc/proc_dff.cc index da2a14c82..234671df5 100644 --- a/passes/proc/proc_dff.cc +++ b/passes/proc/proc_dff.cc @@ -143,48 +143,23 @@ void gen_dffsr_complex(RTLIL::Module *mod, RTLIL::SigSpec sig_d, RTLIL::SigSpec cell->type.c_str(), cell->name.c_str(), clk_polarity ? "positive" : "negative"); } -void gen_dffsr(RTLIL::Module *mod, RTLIL::SigSpec sig_in, RTLIL::SigSpec sig_set, RTLIL::SigSpec sig_out, +void gen_aldff(RTLIL::Module *mod, RTLIL::SigSpec sig_in, RTLIL::SigSpec sig_set, RTLIL::SigSpec sig_out, bool clk_polarity, bool set_polarity, RTLIL::SigSpec clk, RTLIL::SigSpec set, RTLIL::Process *proc) { std::stringstream sstr; sstr << "$procdff$" << (autoidx++); - RTLIL::SigSpec sig_set_inv = mod->addWire(NEW_ID, sig_in.size()); - RTLIL::SigSpec sig_sr_set = mod->addWire(NEW_ID, sig_in.size()); - RTLIL::SigSpec sig_sr_clr = mod->addWire(NEW_ID, sig_in.size()); - - RTLIL::Cell *inv_set = mod->addCell(NEW_ID, ID($not)); - inv_set->parameters[ID::A_SIGNED] = RTLIL::Const(0); - inv_set->parameters[ID::A_WIDTH] = RTLIL::Const(sig_in.size()); - inv_set->parameters[ID::Y_WIDTH] = RTLIL::Const(sig_in.size()); - inv_set->setPort(ID::A, sig_set); - inv_set->setPort(ID::Y, sig_set_inv); - - RTLIL::Cell *mux_sr_set = mod->addCell(NEW_ID, ID($mux)); - mux_sr_set->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size()); - mux_sr_set->setPort(set_polarity ? ID::A : ID::B, RTLIL::Const(0, sig_in.size())); - mux_sr_set->setPort(set_polarity ? ID::B : ID::A, sig_set); - mux_sr_set->setPort(ID::Y, sig_sr_set); - mux_sr_set->setPort(ID::S, set); - - RTLIL::Cell *mux_sr_clr = mod->addCell(NEW_ID, ID($mux)); - mux_sr_clr->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size()); - mux_sr_clr->setPort(set_polarity ? ID::A : ID::B, RTLIL::Const(0, sig_in.size())); - mux_sr_clr->setPort(set_polarity ? ID::B : ID::A, sig_set_inv); - mux_sr_clr->setPort(ID::Y, sig_sr_clr); - mux_sr_clr->setPort(ID::S, set); - - RTLIL::Cell *cell = mod->addCell(sstr.str(), ID($dffsr)); + RTLIL::Cell *cell = mod->addCell(sstr.str(), ID($aldff)); cell->attributes = proc->attributes; + cell->parameters[ID::WIDTH] = RTLIL::Const(sig_in.size()); + cell->parameters[ID::ALOAD_POLARITY] = RTLIL::Const(set_polarity, 1); cell->parameters[ID::CLK_POLARITY] = RTLIL::Const(clk_polarity, 1); - cell->parameters[ID::SET_POLARITY] = RTLIL::Const(true, 1); - cell->parameters[ID::CLR_POLARITY] = RTLIL::Const(true, 1); cell->setPort(ID::D, sig_in); cell->setPort(ID::Q, sig_out); + cell->setPort(ID::AD, sig_set); cell->setPort(ID::CLK, clk); - cell->setPort(ID::SET, sig_sr_set); - cell->setPort(ID::CLR, sig_sr_clr); + cell->setPort(ID::ALOAD, set); log(" created %s cell `%s' with %s edge clock and %s level non-const reset.\n", cell->type.c_str(), cell->name.c_str(), clk_polarity ? "positive" : "negative", set_polarity ? "positive" : "negative"); @@ -355,7 +330,7 @@ void proc_dff(RTLIL::Module *mod, RTLIL::Process *proc, ConstEval &ce) else if (!rstval.is_fully_const() && !ce.eval(rstval)) { log_warning("Async reset value `%s' is not constant!\n", log_signal(rstval)); - gen_dffsr(mod, insig, rstval, sig_q, + gen_aldff(mod, insig, rstval, sig_q, sync_edge->type == RTLIL::SyncType::STp, sync_level && sync_level->type == RTLIL::SyncType::ST1, sync_edge->signal, sync_level->signal, proc); diff --git a/passes/proc/proc_rom.cc b/passes/proc/proc_rom.cc new file mode 100644 index 000000000..b83466ce7 --- /dev/null +++ b/passes/proc/proc_rom.cc @@ -0,0 +1,252 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" +#include "kernel/sigtools.h" +#include "kernel/log.h" +#include "kernel/mem.h" +#include <stdlib.h> +#include <stdio.h> + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct RomWorker +{ + RTLIL::Module *module; + SigMap sigmap; + + int count = 0; + + RomWorker(RTLIL::Module *mod) : module(mod), sigmap(mod) {} + + void do_switch(RTLIL::SwitchRule *sw) + { + for (auto cs : sw->cases) { + do_case(cs); + } + + if (sw->cases.empty()) { + log_debug("rejecting switch: no cases\n"); + return; + } + + // A switch can be converted into ROM when: + // + // 1. No case contains a nested switch + // 2. All cases have the same set of assigned signals + // 3. All right-hand values in cases are constants + // 4. All compare values used in cases are fully-defined constants + // 5. The cases must cover all possible values (possibly by using default case) + + SigSpec lhs; + dict<SigBit, int> lhs_lookup; + for (auto &it: sw->cases[0]->actions) { + for (auto bit: it.first) { + if (!lhs_lookup.count(bit)) { + lhs_lookup[bit] = GetSize(lhs); + lhs.append(bit); + } + } + } + + int swsigbits = 0; + for (int i = 0; i < GetSize(sw->signal); i++) + if (sw->signal[i] != State::S0) + swsigbits = i + 1; + + dict<int, Const> vals; + Const default_val; + bool got_default = false; + int maxaddr = 0; + for (auto cs : sw->cases) { + if (!cs->switches.empty()) { + log_debug("rejecting switch: has nested switches\n"); + return; + } + Const val = Const(State::Sm, GetSize(lhs)); + for (auto &it: cs->actions) { + if (!it.second.is_fully_const()) { + log_debug("rejecting switch: rhs not const\n"); + return; + } + for (int i = 0; i < GetSize(it.first); i++) { + auto it2 = lhs_lookup.find(it.first[i]); + if (it2 == lhs_lookup.end()) { + log_debug("rejecting switch: lhs not uniform\n"); + return; + } + val[it2->second] = it.second[i].data; + } + } + for (auto bit: val.bits) { + if (bit == State::Sm) { + log_debug("rejecting switch: lhs not uniform\n"); + return; + } + } + + for (auto &addr: cs->compare) { + if (!addr.is_fully_def()) { + log_debug("rejecting switch: case value has undef bits\n"); + return; + } + Const c = addr.as_const(); + while (GetSize(c) && c.bits.back() == State::S0) + c.bits.pop_back(); + if (GetSize(c) > swsigbits) + continue; + if (GetSize(c) > 30) { + log_debug("rejecting switch: address too large\n"); + return; + } + int a = c.as_int(); + if (vals.count(a)) + continue; + vals[a] = val; + if (a > maxaddr) + maxaddr = a; + } + if (cs->compare.empty()) { + default_val = val; + got_default = true; + break; + } + } + int abits = ceil_log2(maxaddr + 1); + if (!got_default && (swsigbits > 30 || GetSize(vals) != (1 << swsigbits))) { + log_debug("rejecting switch: not all values are covered\n"); + return; + } + + // TODO: better density heuristic? + if (GetSize(vals) < 8) { + log_debug("rejecting switch: not enough values\n"); + return; + } + if ((1 << abits) / GetSize(vals) > 4) { + log_debug("rejecting switch: not enough density\n"); + return; + } + + // Ok, let's do it. + SigSpec rdata = module->addWire(NEW_ID, GetSize(lhs)); + Mem mem(module, NEW_ID, GetSize(lhs), 0, 1 << abits); + mem.attributes = sw->attributes; + + Const init_data; + for (int i = 0; i < mem.size; i++) { + auto it = vals.find(i); + if (it == vals.end()) { + log_assert(got_default); + for (auto bit: default_val.bits) + init_data.bits.push_back(bit); + } else { + for (auto bit: it->second.bits) + init_data.bits.push_back(bit); + } + } + + MemInit init; + init.addr = 0; + init.data = init_data; + init.en = Const(State::S1, GetSize(lhs)); + mem.inits.push_back(std::move(init)); + + MemRd rd; + rd.addr = sw->signal.extract(0, abits); + rd.data = rdata; + rd.init_value = Const(State::Sx, GetSize(lhs)); + rd.arst_value = Const(State::Sx, GetSize(lhs)); + rd.srst_value = Const(State::Sx, GetSize(lhs)); + mem.rd_ports.push_back(std::move(rd)); + + mem.emit(); + for (auto cs: sw->cases) + delete cs; + sw->cases.clear(); + sw->signal = sw->signal.extract(0, swsigbits); + if (abits == GetSize(sw->signal)) { + sw->signal = SigSpec(); + RTLIL::CaseRule *cs = new RTLIL::CaseRule; + cs->actions.push_back(SigSig(lhs, rdata)); + sw->cases.push_back(cs); + } else { + sw->signal = sw->signal.extract_end(abits); + RTLIL::CaseRule *cs = new RTLIL::CaseRule; + cs->compare.push_back(Const(State::S0, GetSize(sw->signal))); + cs->actions.push_back(SigSig(lhs, rdata)); + sw->cases.push_back(cs); + RTLIL::CaseRule *cs2 = new RTLIL::CaseRule; + cs2->actions.push_back(SigSig(lhs, default_val)); + sw->cases.push_back(cs2); + } + + count += 1; + } + + void do_case(RTLIL::CaseRule *cs) + { + for (auto sw: cs->switches) { + do_switch(sw); + } + } + + void do_process(RTLIL::Process *pr) + { + do_case(&pr->root_case); + } +}; + +struct ProcRomPass : public Pass { + ProcRomPass() : Pass("proc_rom", "convert switches to ROMs") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" proc_rom [selection]\n"); + log("\n"); + log("This pass converts switches into read-only memories when appropriate.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + int total_count = 0; + log_header(design, "Executing PROC_ROM pass (convert switches to ROMs).\n"); + + extra_args(args, 1, design); + + for (auto mod : design->modules()) { + if (!design->selected(mod)) + continue; + RomWorker worker(mod); + for (auto &proc_it : mod->processes) { + if (!design->selected(mod, proc_it.second)) + continue; + worker.do_process(proc_it.second); + } + total_count += worker.count; + } + + log("Converted %d switch%s.\n", + total_count, total_count == 1 ? "" : "es"); + } +} ProcRomPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/sat/Makefile.inc b/passes/sat/Makefile.inc index 7118c1563..da6d49433 100644 --- a/passes/sat/Makefile.inc +++ b/passes/sat/Makefile.inc @@ -2,7 +2,9 @@ OBJS += passes/sat/sat.o OBJS += passes/sat/freduce.o OBJS += passes/sat/eval.o +ifeq ($(ENABLE_ZLIB),1) OBJS += passes/sat/sim.o +endif OBJS += passes/sat/miter.o OBJS += passes/sat/expose.o OBJS += passes/sat/assertpmux.o diff --git a/passes/sat/async2sync.cc b/passes/sat/async2sync.cc index a2b51677e..46c76eba9 100644 --- a/passes/sat/async2sync.cc +++ b/passes/sat/async2sync.cc @@ -41,8 +41,6 @@ struct Async2syncPass : public Pass { log("reset value in the next cycle regardless of the data-in value at the time of\n"); log("the clock edge.\n"); log("\n"); - log("Currently only $adff, $dffsr, and $dlatch cells are supported by this pass.\n"); - log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { @@ -74,16 +72,13 @@ struct Async2syncPass : public Pass { FfData ff(&initvals, cell); // Skip for $_FF_ and $ff cells. - if (ff.has_d && !ff.has_clk && !ff.has_en) + if (ff.has_gclk) continue; if (ff.has_clk) { - if (!ff.has_sr && !ff.has_arst) - continue; - if (ff.has_sr) { - ff.unmap_ce_srst(module); + ff.unmap_ce_srst(); log("Replacing %s.%s (%s): SET=%s, CLR=%s, D=%s, Q=%s\n", log_id(module), log_id(cell), log_id(cell->type), @@ -128,8 +123,41 @@ struct Async2syncPass : public Pass { ff.sig_d = new_d; ff.sig_q = new_q; ff.has_sr = false; + } else if (ff.has_aload) { + ff.unmap_ce_srst(); + + log("Replacing %s.%s (%s): ALOAD=%s, AD=%s, D=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_d), log_signal(ff.sig_q)); + + initvals.remove_init(ff.sig_q); + + Wire *new_d = module->addWire(NEW_ID, ff.width); + Wire *new_q = module->addWire(NEW_ID, ff.width); + + if (ff.pol_aload) { + if (!ff.is_fine) { + module->addMux(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, ff.sig_q); + module->addMux(NEW_ID, ff.sig_d, ff.sig_ad, ff.sig_aload, new_d); + } else { + module->addMuxGate(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, ff.sig_q); + module->addMuxGate(NEW_ID, ff.sig_d, ff.sig_ad, ff.sig_aload, new_d); + } + } else { + if (!ff.is_fine) { + module->addMux(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, ff.sig_q); + module->addMux(NEW_ID, ff.sig_ad, ff.sig_d, ff.sig_aload, new_d); + } else { + module->addMuxGate(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, ff.sig_q); + module->addMuxGate(NEW_ID, ff.sig_ad, ff.sig_d, ff.sig_aload, new_d); + } + } + + ff.sig_d = new_d; + ff.sig_q = new_q; + ff.has_aload = false; } else if (ff.has_arst) { - ff.unmap_srst(module); + ff.unmap_srst(); log("Replacing %s.%s (%s): ARST=%s, D=%s, Q=%s\n", log_id(module), log_id(cell), log_id(cell->type), @@ -154,9 +182,12 @@ struct Async2syncPass : public Pass { ff.sig_q = new_q; ff.has_arst = false; ff.has_srst = true; + ff.ce_over_srst = false; ff.val_srst = ff.val_arst; ff.sig_srst = ff.sig_arst; ff.pol_srst = ff.pol_arst; + } else { + continue; } } else @@ -164,25 +195,25 @@ struct Async2syncPass : public Pass { // Latch. log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n", log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_en), log_signal(ff.sig_d), log_signal(ff.sig_q)); + log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q)); initvals.remove_init(ff.sig_q); Wire *new_q = module->addWire(NEW_ID, ff.width); Wire *new_d; - if (ff.has_d) { + if (ff.has_aload) { new_d = module->addWire(NEW_ID, ff.width); - if (ff.pol_en) { + if (ff.pol_aload) { if (!ff.is_fine) - module->addMux(NEW_ID, new_q, ff.sig_d, ff.sig_en, new_d); + module->addMux(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, new_d); else - module->addMuxGate(NEW_ID, new_q, ff.sig_d, ff.sig_en, new_d); + module->addMuxGate(NEW_ID, new_q, ff.sig_ad, ff.sig_aload, new_d); } else { if (!ff.is_fine) - module->addMux(NEW_ID, ff.sig_d, new_q, ff.sig_en, new_d); + module->addMux(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, new_d); else - module->addMuxGate(NEW_ID, ff.sig_d, new_q, ff.sig_en, new_d); + module->addMuxGate(NEW_ID, ff.sig_ad, new_q, ff.sig_aload, new_d); } } else { new_d = new_q; @@ -231,15 +262,12 @@ struct Async2syncPass : public Pass { ff.sig_d = new_d; ff.sig_q = new_q; - ff.has_en = false; + ff.has_aload = false; ff.has_arst = false; ff.has_sr = false; - ff.has_d = true; + ff.has_gclk = true; } - - IdString name = cell->name; - module->remove(cell); - ff.emit(module, name); + ff.emit(); } } } diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index 062083972..b1b0567a0 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -39,8 +39,14 @@ struct Clk2fflogicPass : public Pass { log("multiple clocks.\n"); log("\n"); } - SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity) { - Wire *past_sig = module->addWire(NEW_ID, GetSize(sig)); + SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, bool is_fine, IdString past_sig_id) { + if (!is_fine) + return wrap_async_control(module, sig, polarity, past_sig_id); + return wrap_async_control_gate(module, sig, polarity, past_sig_id); + } + SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) { + Wire *past_sig = module->addWire(past_sig_id, GetSize(sig)); + past_sig->attributes[ID::init] = RTLIL::Const(polarity ? State::S0 : State::S1, GetSize(sig)); module->addFf(NEW_ID, sig, past_sig); if (polarity) sig = module->Or(NEW_ID, sig, past_sig); @@ -51,8 +57,9 @@ struct Clk2fflogicPass : public Pass { else return module->Not(NEW_ID, sig); } - SigSpec wrap_async_control_gate(Module *module, SigSpec sig, bool polarity) { - Wire *past_sig = module->addWire(NEW_ID); + SigSpec wrap_async_control_gate(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) { + Wire *past_sig = module->addWire(past_sig_id); + past_sig->attributes[ID::init] = polarity ? State::S0 : State::S1; module->addFfGate(NEW_ID, sig, past_sig); if (polarity) sig = module->OrGate(NEW_ID, sig, past_sig); @@ -105,7 +112,7 @@ struct Clk2fflogicPass : public Pass { i, log_id(module), log_id(mem.memid), log_signal(port.clk), log_signal(port.addr), log_signal(port.data)); - Wire *past_clk = module->addWire(NEW_ID); + Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#past_clk#%s", log_id(mem.memid), i, log_signal(port.clk)))); past_clk->attributes[ID::init] = port.clk_polarity ? State::S1 : State::S0; module->addFf(NEW_ID, port.clk, past_clk); @@ -121,13 +128,13 @@ struct Clk2fflogicPass : public Pass { SigSpec clock_edge = module->Eqx(NEW_ID, {port.clk, SigSpec(past_clk)}, clock_edge_pattern); - SigSpec en_q = module->addWire(NEW_ID, GetSize(port.en)); + SigSpec en_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#en_q", log_id(mem.memid), i)), GetSize(port.en)); module->addFf(NEW_ID, port.en, en_q); - SigSpec addr_q = module->addWire(NEW_ID, GetSize(port.addr)); + SigSpec addr_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#addr_q", log_id(mem.memid), i)), GetSize(port.addr)); module->addFf(NEW_ID, port.addr, addr_q); - SigSpec data_q = module->addWire(NEW_ID, GetSize(port.data)); + SigSpec data_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#%d#data_q", log_id(mem.memid), i)), GetSize(port.data)); module->addFf(NEW_ID, port.data, data_q); port.clk = State::S0; @@ -148,12 +155,36 @@ struct Clk2fflogicPass : public Pass { if (RTLIL::builtin_ff_cell_types().count(cell->type)) { FfData ff(&initvals, cell); - if (ff.has_d && !ff.has_clk && !ff.has_en) { + if (ff.has_gclk) { // Already a $ff or $_FF_ cell. continue; } - Wire *past_q = module->addWire(NEW_ID, ff.width); + if (ff.has_clk) { + log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q)); + } else if (ff.has_aload) { + log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q)); + } else { + // $sr. + log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q)); + } + + ff.remove(); + + // Strip spaces from signal name, since Yosys IDs can't contain spaces + // Spaces only occur when we have a signal that's a slice of a larger bus, + // e.g. "\myreg [5:0]", so removing spaces shouldn't result in loss of uniqueness + std::string sig_q_str = log_signal(ff.sig_q); + sig_q_str.erase(std::remove(sig_q_str.begin(), sig_q_str.end(), ' '), sig_q_str.end()); + + Wire *past_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_q_wire", sig_q_str.c_str())), ff.width); + if (!ff.is_fine) { module->addFf(NEW_ID, ff.sig_q, past_q); } else { @@ -163,9 +194,9 @@ struct Clk2fflogicPass : public Pass { initvals.set_init(past_q, ff.val_init); if (ff.has_clk) { - ff.unmap_ce_srst(module); + ff.unmap_ce_srst(); - Wire *past_clk = module->addWire(NEW_ID); + Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_clk#%s", sig_q_str.c_str(), log_signal(ff.sig_clk)))); initvals.set_init(past_clk, ff.pol_clk ? State::S1 : State::S0); if (!ff.is_fine) @@ -173,10 +204,6 @@ struct Clk2fflogicPass : public Pass { else module->addFfGate(NEW_ID, ff.sig_clk, past_clk); - log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q)); - SigSpec clock_edge_pattern; if (ff.pol_clk) { @@ -189,7 +216,7 @@ struct Clk2fflogicPass : public Pass { SigSpec clock_edge = module->Eqx(NEW_ID, {ff.sig_clk, SigSpec(past_clk)}, clock_edge_pattern); - Wire *past_d = module->addWire(NEW_ID, ff.width); + Wire *past_d = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_d_wire", sig_q_str.c_str())), ff.width); if (!ff.is_fine) module->addFf(NEW_ID, ff.sig_d, past_d); else @@ -202,30 +229,22 @@ struct Clk2fflogicPass : public Pass { qval = module->Mux(NEW_ID, past_q, past_d, clock_edge); else qval = module->MuxGate(NEW_ID, past_q, past_d, clock_edge); - } else if (ff.has_d) { - - log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_en), log_signal(ff.sig_d), log_signal(ff.sig_q)); + } else { + qval = past_q; + } - SigSpec sig_en = wrap_async_control(module, ff.sig_en, ff.pol_en); + if (ff.has_aload) { + SigSpec sig_aload = wrap_async_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine, NEW_ID); if (!ff.is_fine) - qval = module->Mux(NEW_ID, past_q, ff.sig_d, sig_en); + qval = module->Mux(NEW_ID, qval, ff.sig_ad, sig_aload); else - qval = module->MuxGate(NEW_ID, past_q, ff.sig_d, sig_en); - } else { - - log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q)); - - qval = past_q; + qval = module->MuxGate(NEW_ID, qval, ff.sig_ad, sig_aload); } if (ff.has_sr) { - SigSpec setval = wrap_async_control(module, ff.sig_set, ff.pol_set); - SigSpec clrval = wrap_async_control(module, ff.sig_clr, ff.pol_clr); + SigSpec setval = wrap_async_control(module, ff.sig_set, ff.pol_set, ff.is_fine, NEW_ID); + SigSpec clrval = wrap_async_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine, NEW_ID); if (!ff.is_fine) { clrval = module->Not(NEW_ID, clrval); qval = module->Or(NEW_ID, qval, setval); @@ -236,7 +255,8 @@ struct Clk2fflogicPass : public Pass { module->addAndGate(NEW_ID, qval, clrval, ff.sig_q); } } else if (ff.has_arst) { - SigSpec arst = wrap_async_control(module, ff.sig_arst, ff.pol_arst); + IdString id = NEW_ID_SUFFIX(stringf("%s#past_arst#%s", sig_q_str.c_str(), log_signal(ff.sig_arst))); + SigSpec arst = wrap_async_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine, id); if (!ff.is_fine) module->addMux(NEW_ID, qval, ff.val_arst, arst, ff.sig_q); else @@ -244,10 +264,6 @@ struct Clk2fflogicPass : public Pass { } else { module->connect(ff.sig_q, qval); } - - initvals.remove_init(ff.sig_q); - module->remove(cell); - continue; } } } diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 4e158da62..d085fab2d 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -21,19 +21,77 @@ #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/mem.h" +#include "kernel/fstdata.h" +#include "kernel/ff.h" #include <ctime> USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +enum class SimulationMode { + sim, + cmp, + gold, + gate, +}; + +static const std::map<std::string, int> g_units = +{ + { "", -9 }, // default is ns + { "s", 0 }, + { "ms", -3 }, + { "us", -6 }, + { "ns", -9 }, + { "ps", -12 }, + { "fs", -15 }, + { "as", -18 }, + { "zs", -21 }, +}; + +static double stringToTime(std::string str) +{ + if (str=="END") return -1; + + char *endptr; + long value = strtol(str.c_str(), &endptr, 10); + + if (g_units.find(endptr)==g_units.end()) + log_error("Cannot parse '%s', bad unit '%s'\n", str.c_str(), endptr); + + if (value < 0) + log_error("Time value '%s' must be positive\n", str.c_str()); + + return value * pow(10.0, g_units.at(endptr)); +} + +struct SimWorker; +struct OutputWriter +{ + OutputWriter(SimWorker *w) { worker = w;}; + virtual ~OutputWriter() {}; + virtual void write(std::map<int, bool> &use_signal) = 0; + SimWorker *worker; +}; + struct SimShared { bool debug = false; + bool verbose = true; bool hide_internal = true; bool writeback = false; bool zinit = false; int rstlen = 1; + FstData *fst = nullptr; + double start_time = 0; + double stop_time = -1; + SimulationMode sim_mode = SimulationMode::sim; + bool cycles_set = false; + std::vector<std::unique_ptr<OutputWriter>> outputfiles; + std::vector<std::pair<int,std::map<int,Const>>> output_data; + bool ignore_x = false; + bool date = false; + bool multiclock = false; }; void zinit(State &v) @@ -51,7 +109,8 @@ void zinit(Const &v) struct SimInstance { SimShared *shared; - + + std::string scope; Module *module; Cell *instance; @@ -70,8 +129,13 @@ struct SimInstance struct ff_state_t { - State past_clock; Const past_d; + Const past_ad; + State past_clk; + State past_ce; + State past_srst; + + FfData data; }; struct mem_state_t @@ -91,10 +155,12 @@ struct SimInstance std::vector<Mem> memories; - dict<Wire*, pair<int, Const>> vcd_database; + dict<Wire*, pair<int, Const>> signal_database; + dict<Wire*, fstHandle> fst_handles; + dict<IdString, dict<int,fstHandle>> fst_memories; - SimInstance(SimShared *shared, Module *module, Cell *instance = nullptr, SimInstance *parent = nullptr) : - shared(shared), module(module), instance(instance), parent(parent), sigmap(module) + SimInstance(SimShared *shared, std::string scope, Module *module, Cell *instance = nullptr, SimInstance *parent = nullptr) : + shared(shared), scope(scope), module(module), instance(instance), parent(parent), sigmap(module) { log_assert(module); @@ -116,6 +182,13 @@ struct SimInstance } } + if ((shared->fst) && !(shared->hide_internal && wire->name[0] == '$')) { + fstHandle id = shared->fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name)); + if (id==0 && wire->name.isPublic()) + log_warning("Unable to find wire %s in input file.\n", (scope + "." + RTLIL::unescape_id(wire->name)).c_str()); + fst_handles[wire] = id; + } + if (wire->attributes.count(ID::init)) { Const initval = wire->attributes.at(ID::init); for (int i = 0; i < GetSize(sig) && i < GetSize(initval); i++) @@ -144,7 +217,7 @@ struct SimInstance Module *mod = module->design->module(cell->type); if (mod != nullptr) { - dirty_children.insert(new SimInstance(shared, mod, cell, this)); + dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this)); } for (auto &port : cell->connections()) { @@ -157,16 +230,24 @@ struct SimInstance } } - if (cell->type.in(ID($dff))) { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff_data(nullptr, cell); ff_state_t ff; - ff.past_clock = State::Sx; - ff.past_d = Const(State::Sx, cell->getParam(ID::WIDTH).as_int()); + ff.past_d = Const(State::Sx, ff_data.width); + ff.past_ad = Const(State::Sx, ff_data.width); + ff.past_clk = State::Sx; + ff.past_ce = State::Sx; + ff.past_srst = State::Sx; + ff.data = ff_data; ff_database[cell] = ff; } if (cell->is_mem_cell()) { - mem_cells[cell] = cell->parameters.at(ID::MEMID).decode_string(); + std::string name = cell->parameters.at(ID::MEMID).decode_string(); + mem_cells[cell] = name; + if (shared->fst) + fst_memories[name] = shared->fst->getMemoryHandles(scope + "." + RTLIL::unescape_id(name)); } if (cell->type.in(ID($assert), ID($cover), ID($assume))) { formal_database.insert(cell); @@ -177,11 +258,11 @@ struct SimInstance { for (auto &it : ff_database) { - Cell *cell = it.first; ff_state_t &ff = it.second; zinit(ff.past_d); + zinit(ff.past_ad); - SigSpec qsig = cell->getPort(ID::Q); + SigSpec qsig = it.second.data.sig_q; Const qdata = get_state(qsig); zinit(qdata); set_state(qsig, qdata); @@ -253,6 +334,24 @@ struct SimInstance return did_something; } + void set_memory_state(IdString memid, Const addr, Const data) + { + auto &state = mem_database[memid]; + + int offset = (addr.as_int() - state.mem->start_offset) * state.mem->width; + for (int i = 0; i < GetSize(data); i++) + if (0 <= i+offset && i+offset < state.mem->size * state.mem->width) + state.data.bits[i+offset] = data.bits[i]; + } + + void set_memory_state_bit(IdString memid, int offset, State data) + { + auto &state = mem_database[memid]; + if (offset >= state.mem->size * state.mem->width) + log_error("Addressing out of bounds bit %d/%d of memory %s\n", offset, state.mem->size * state.mem->width, log_id(memid)); + state.data.bits[offset] = data; + } + void update_cell(Cell *cell) { if (ff_database.count(cell)) @@ -313,6 +412,12 @@ struct SimInstance return; } + // (A,S -> Y) cells + if (has_a && !has_b && !has_c && !has_d && has_s && has_y) { + set_state(sig_y, CellTypes::eval(cell, get_state(sig_a), get_state(sig_s))); + return; + } + // (A,B,S -> Y) cells if (has_a && has_b && !has_c && !has_d && has_s && has_y) { set_state(sig_y, CellTypes::eval(cell, get_state(sig_a), get_state(sig_b), get_state(sig_s))); @@ -408,21 +513,62 @@ struct SimInstance for (auto &it : ff_database) { - Cell *cell = it.first; ff_state_t &ff = it.second; - - if (cell->type.in(ID($dff))) - { - bool clkpol = cell->getParam(ID::CLK_POLARITY).as_bool(); - State current_clock = get_state(cell->getPort(ID::CLK))[0]; - - if (clkpol ? (ff.past_clock == State::S1 || current_clock != State::S1) : - (ff.past_clock == State::S0 || current_clock != State::S0)) - continue; - - if (set_state(cell->getPort(ID::Q), ff.past_d)) - did_something = true; + FfData &ff_data = ff.data; + + Const current_q = get_state(ff.data.sig_q); + + if (ff_data.has_clk) { + // flip-flops + State current_clk = get_state(ff_data.sig_clk)[0]; + if (ff_data.pol_clk ? (ff.past_clk == State::S0 && current_clk != State::S0) : + (ff.past_clk == State::S1 && current_clk != State::S1)) { + bool ce = ff.past_ce == (ff_data.pol_ce ? State::S1 : State::S0); + // set if no ce, or ce is enabled + if (!ff_data.has_ce || (ff_data.has_ce && ce)) { + current_q = ff.past_d; + } + // override if sync reset + if ((ff_data.has_srst) && (ff.past_srst == (ff_data.pol_srst ? State::S1 : State::S0)) && + ((!ff_data.ce_over_srst) || (ff_data.ce_over_srst && ce))) { + current_q = ff_data.val_srst; + } + } + } + // async load + if (ff_data.has_aload) { + State current_aload = get_state(ff_data.sig_aload)[0]; + if (current_aload == (ff_data.pol_aload ? State::S1 : State::S0)) { + current_q = ff_data.has_clk ? ff.past_ad : get_state(ff.data.sig_ad); + } + } + // async reset + if (ff_data.has_arst) { + State current_arst = get_state(ff_data.sig_arst)[0]; + if (current_arst == (ff_data.pol_arst ? State::S1 : State::S0)) { + current_q = ff_data.val_arst; + } + } + // handle set/reset + if (ff.data.has_sr) { + Const current_clr = get_state(ff.data.sig_clr); + Const current_set = get_state(ff.data.sig_set); + + for(int i=0;i<ff.past_d.size();i++) { + if (current_clr[i] == (ff_data.pol_clr ? State::S1 : State::S0)) { + current_q[i] = State::S0; + } + else if (current_set[i] == (ff_data.pol_set ? State::S1 : State::S0)) { + current_q[i] = State::S1; + } + } + } + if (ff_data.has_gclk) { + // $ff + current_q = ff.past_d; } + if (set_state(ff_data.sig_q, current_q)) + did_something = true; } for (auto &it : mem_database) @@ -480,13 +626,22 @@ struct SimInstance { for (auto &it : ff_database) { - Cell *cell = it.first; ff_state_t &ff = it.second; - if (cell->type.in(ID($dff))) { - ff.past_clock = get_state(cell->getPort(ID::CLK))[0]; - ff.past_d = get_state(cell->getPort(ID::D)); - } + if (ff.data.has_aload) + ff.past_ad = get_state(ff.data.sig_ad); + + if (ff.data.has_clk || ff.data.has_gclk) + ff.past_d = get_state(ff.data.sig_d); + + if (ff.data.has_clk) + ff.past_clk = get_state(ff.data.sig_clk)[0]; + + if (ff.data.has_ce) + ff.past_ce = get_state(ff.data.sig_ce)[0]; + + if (ff.data.has_srst) + ff.past_srst = get_state(ff.data.sig_srst)[0]; } for (auto &it : mem_database) @@ -537,8 +692,7 @@ struct SimInstance for (auto &it : ff_database) { - Cell *cell = it.first; - SigSpec sig_q = cell->getPort(ID::Q); + SigSpec sig_q = it.second.data.sig_q; Const initval = get_state(sig_q); for (int i = 0; i < GetSize(sig_q); i++) @@ -568,28 +722,51 @@ struct SimInstance it.second->writeback(wbmods); } - void write_vcd_header(std::ofstream &f, int &id) + void register_signals(int &id) { - f << stringf("$scope module %s $end\n", log_id(name())); - for (auto wire : module->wires()) { if (shared->hide_internal && wire->name[0] == '$') continue; - f << stringf("$var wire %d n%d %s%s $end\n", GetSize(wire), id, wire->name[0] == '$' ? "\\" : "", log_id(wire)); - vcd_database[wire] = make_pair(id++, Const()); + signal_database[wire] = make_pair(id, Const()); + id++; } for (auto child : children) - child.second->write_vcd_header(f, id); + child.second->register_signals(id); + } + + void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(Wire*, int, bool)> register_signal) + { + enter_scope(name()); - f << stringf("$upscope $end\n"); + dict<Wire*,bool> registers; + for (auto cell : module->cells()) + { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff_data(nullptr, cell); + SigSpec q = sigmap(ff_data.sig_q); + if (q.is_wire() && signal_database.count(q.as_wire()) != 0) { + registers[q.as_wire()] = true; + } + } + } + + for (auto signal : signal_database) + { + register_signal(signal.first, signal.second.first, registers.count(signal.first)!=0); + } + + for (auto child : children) + child.second->write_output_header(enter_scope, exit_scope, register_signal); + + exit_scope(); } - void write_vcd_step(std::ofstream &f) + void register_output_step_values(std::map<int,Const> *data) { - for (auto &it : vcd_database) + for (auto &it : signal_database) { Wire *wire = it.first; Const value = get_state(wire); @@ -599,66 +776,189 @@ struct SimInstance continue; it.second.second = value; + data->emplace(id, value); + } + + for (auto child : children) + child.second->register_output_step_values(data); + } + + bool setInitState() + { + bool did_something = false; + for(auto &item : fst_handles) { + if (item.second==0) continue; // Ignore signals not found + std::string v = shared->fst->valueOf(item.second); + did_something |= set_state(item.first, Const::from_string(v)); + } + for (auto &it : ff_database) + { + ff_state_t &ff = it.second; + SigSpec dsig = it.second.data.sig_d; + Const value = get_state(dsig); + if (dsig.is_wire()) { + ff.past_d = value; + if (ff.data.has_aload) + ff.past_ad = value; + did_something |= true; + } + } + for (auto cell : module->cells()) + { + if (cell->is_mem_cell()) { + std::string memid = cell->parameters.at(ID::MEMID).decode_string(); + for (auto &data : fst_memories[memid]) + { + std::string v = shared->fst->valueOf(data.second); + set_memory_state(memid, Const(data.first), Const::from_string(v)); + } + } + } + + for (auto child : children) + did_something |= child.second->setInitState(); + return did_something; + } - f << "b"; - for (int i = GetSize(value)-1; i >= 0; i--) { - switch (value[i]) { - case State::S0: f << "0"; break; - case State::S1: f << "1"; break; - case State::Sx: f << "x"; break; - default: f << "z"; + void addAdditionalInputs(std::map<Wire*,fstHandle> &inputs) + { + for (auto cell : module->cells()) + { + if (cell->type.in(ID($anyseq))) { + SigSpec sig_y = sigmap(cell->getPort(ID::Y)); + if (sig_y.is_wire()) { + bool found = false; + for(auto &item : fst_handles) { + if (item.second==0) continue; // Ignore signals not found + if (sig_y == sigmap(item.first)) { + inputs[sig_y.as_wire()] = item.second; + found = true; + break; + } + } + if (!found) + log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(sig_y.as_wire()->name)).c_str()); } } + } + for (auto child : children) + child.second->addAdditionalInputs(inputs); + } - f << stringf(" n%d\n", id); + void setState(dict<int, std::pair<SigBit,bool>> bits, std::string values) + { + for(auto bit : bits) { + if (bit.first >= GetSize(values)) + log_error("Too few input data bits in file.\n"); + switch(values.at(bit.first)) { + case '0': set_state(bit.second.first, bit.second.second ? State::S1 : State::S0); break; + case '1': set_state(bit.second.first, bit.second.second ? State::S0 : State::S1); break; + default: set_state(bit.second.first, State::Sx); break; + } } + } + + void setMemState(dict<int, std::pair<std::string,int>> bits, std::string values) + { + for(auto bit : bits) { + if (bit.first >= GetSize(values)) + log_error("Too few input data bits in file.\n"); + switch(values.at(bit.first)) { + case '0': set_memory_state_bit(bit.second.first, bit.second.second, State::S0); break; + case '1': set_memory_state_bit(bit.second.first, bit.second.second, State::S1); break; + default: set_memory_state_bit(bit.second.first, bit.second.second, State::Sx); break; + } + } + } + bool checkSignals() + { + bool retVal = false; + for(auto &item : fst_handles) { + if (item.second==0) continue; // Ignore signals not found + Const fst_val = Const::from_string(shared->fst->valueOf(item.second)); + Const sim_val = get_state(item.first); + if (sim_val.size()!=fst_val.size()) { + log_warning("Signal '%s.%s' size is different in gold and gate.\n", scope.c_str(), log_id(item.first)); + continue; + } + if (shared->sim_mode == SimulationMode::sim) { + // No checks performed when using stimulus + } else if (shared->sim_mode == SimulationMode::gate && !fst_val.is_fully_def()) { // FST data contains X + for(int i=0;i<fst_val.size();i++) { + if (fst_val[i]!=State::Sx && fst_val[i]!=sim_val[i]) { + log_warning("Signal '%s.%s' in file %s in simulation %s\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val)); + retVal = true; + break; + } + } + } else if (shared->sim_mode == SimulationMode::gold && !sim_val.is_fully_def()) { // sim data contains X + for(int i=0;i<sim_val.size();i++) { + if (sim_val[i]!=State::Sx && fst_val[i]!=sim_val[i]) { + log_warning("Signal '%s.%s' in file %s in simulation %s\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val)); + retVal = true; + break; + } + } + } else { + if (fst_val!=sim_val) { + log_warning("Signal '%s.%s' in file %s in simulation '%s'\n", scope.c_str(), log_id(item.first), log_signal(fst_val), log_signal(sim_val)); + retVal = true; + } + } + } for (auto child : children) - child.second->write_vcd_step(f); + retVal |= child.second->checkSignals(); + return retVal; } }; struct SimWorker : SimShared { SimInstance *top = nullptr; - std::ofstream vcdfile; pool<IdString> clock, clockn, reset, resetn; std::string timescale; + std::string sim_filename; + std::string map_filename; + std::string scope; ~SimWorker() { + outputfiles.clear(); delete top; } - void write_vcd_header() + void register_signals() { - if (!vcdfile.is_open()) - return; - - vcdfile << stringf("$version %s $end\n", yosys_version_str); - - std::time_t t = std::time(nullptr); - char mbstr[255]; - if (std::strftime(mbstr, sizeof(mbstr), "%c", std::localtime(&t))) { - vcdfile << stringf("$date ") << mbstr << stringf(" $end\n"); - } - - if (!timescale.empty()) - vcdfile << stringf("$timescale %s $end\n", timescale.c_str()); - int id = 1; - top->write_vcd_header(vcdfile, id); - - vcdfile << stringf("$enddefinitions $end\n"); + top->register_signals(id); } - void write_vcd_step(int t) + void register_output_step(int t) { - if (!vcdfile.is_open()) - return; + std::map<int,Const> data; + top->register_output_step_values(&data); + output_data.emplace_back(t, data); + } - vcdfile << stringf("#%d\n", t); - top->write_vcd_step(vcdfile); + void write_output_files() + { + std::map<int, bool> use_signal; + bool first = ignore_x; + for(auto& d : output_data) + { + if (first) { + for (auto &data : d.second) + use_signal[data.first] = !data.second.is_fully_undef(); + first = false; + } else { + for (auto &data : d.second) + use_signal[data.first] = true; + } + if (!ignore_x) break; + } + for(auto& writer : outputfiles) + writer->write(use_signal); } void update() @@ -699,11 +999,12 @@ struct SimWorker : SimShared void run(Module *topmod, int numcycles) { log_assert(top == nullptr); - top = new SimInstance(this, topmod); + top = new SimInstance(this, scope, topmod); + register_signals(); if (debug) log("\n===== 0 =====\n"); - else + else if (verbose) log("Simulating cycle 0.\n"); set_inports(reset, State::S1); @@ -714,24 +1015,24 @@ struct SimWorker : SimShared update(); - write_vcd_header(); - write_vcd_step(0); + register_output_step(0); for (int cycle = 0; cycle < numcycles; cycle++) { if (debug) log("\n===== %d =====\n", 10*cycle + 5); - + else if (verbose) + log("Simulating cycle %d.\n", (cycle*2)+1); set_inports(clock, State::S0); set_inports(clockn, State::S1); update(); - write_vcd_step(10*cycle + 5); + register_output_step(10*cycle + 5); if (debug) log("\n===== %d =====\n", 10*cycle + 10); - else - log("Simulating cycle %d.\n", cycle+1); + else if (verbose) + log("Simulating cycle %d.\n", (cycle*2)+2); set_inports(clock, State::S1); set_inports(clockn, State::S0); @@ -742,18 +1043,878 @@ struct SimWorker : SimShared } update(); - write_vcd_step(10*cycle + 10); + register_output_step(10*cycle + 10); + } + + register_output_step(10*numcycles + 2); + + write_output_files(); + + if (writeback) { + pool<Module*> wbmods; + top->writeback(wbmods); + } + } + + void run_cosim_fst(Module *topmod, int numcycles) + { + log_assert(top == nullptr); + fst = new FstData(sim_filename); + + if (scope.empty()) + log_error("Scope must be defined for co-simulation.\n"); + + top = new SimInstance(this, scope, topmod); + register_signals(); + + std::vector<fstHandle> fst_clock; + + for (auto portname : clock) + { + Wire *w = topmod->wire(portname); + if (!w) + log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module)); + if (!w->port_input) + log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module)); + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname)); + if (id==0) + log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname)); + fst_clock.push_back(id); + } + for (auto portname : clockn) + { + Wire *w = topmod->wire(portname); + if (!w) + log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module)); + if (!w->port_input) + log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module)); + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname)); + if (id==0) + log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname)); + fst_clock.push_back(id); } - write_vcd_step(10*numcycles + 2); + SigMap sigmap(topmod); + std::map<Wire*,fstHandle> inputs; + + for (auto wire : topmod->wires()) { + if (wire->port_input) { + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name)); + if (id==0) + log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)).c_str()); + inputs[wire] = id; + } + } + + top->addAdditionalInputs(inputs); + + uint64_t startCount = 0; + uint64_t stopCount = 0; + if (start_time==0) { + if (start_time < fst->getStartTime()) + log_warning("Start time is before simulation file start time\n"); + startCount = fst->getStartTime(); + } else if (start_time==-1) + startCount = fst->getEndTime(); + else { + startCount = start_time / fst->getTimescale(); + if (startCount > fst->getEndTime()) { + startCount = fst->getEndTime(); + log_warning("Start time is after simulation file end time\n"); + } + } + if (stop_time==0) { + if (stop_time < fst->getStartTime()) + log_warning("Stop time is before simulation file start time\n"); + stopCount = fst->getStartTime(); + } else if (stop_time==-1) + stopCount = fst->getEndTime(); + else { + stopCount = stop_time / fst->getTimescale(); + if (stopCount > fst->getEndTime()) { + stopCount = fst->getEndTime(); + log_warning("Stop time is after simulation file end time\n"); + } + } + if (stopCount<startCount) { + log_error("Stop time is before start time\n"); + } + + bool initial = true; + int cycle = 0; + log("Co-simulation from %lu%s to %lu%s", (unsigned long)startCount, fst->getTimescaleString(), (unsigned long)stopCount, fst->getTimescaleString()); + if (cycles_set) + log(" for %d clock cycle(s)",numcycles); + log("\n"); + bool all_samples = fst_clock.empty(); + + try { + fst->reconstructAllAtTimes(fst_clock, startCount, stopCount, [&](uint64_t time) { + if (verbose) + log("Co-simulating %s %d [%lu%s].\n", (all_samples ? "sample" : "cycle"), cycle, (unsigned long)time, fst->getTimescaleString()); + bool did_something = false; + for(auto &item : inputs) { + std::string v = fst->valueOf(item.second); + did_something |= top->set_state(item.first, Const::from_string(v)); + } + + if (initial) { + did_something |= top->setInitState(); + initial = false; + } + if (did_something) + update(); + register_output_step(time); + + bool status = top->checkSignals(); + if (status) + log_error("Signal difference\n"); + cycle++; + + // Limit to number of cycles if provided + if (cycles_set && cycle > numcycles *2) + throw fst_end_of_data_exception(); + if (time==stopCount) + throw fst_end_of_data_exception(); + }); + } catch(fst_end_of_data_exception) { + // end of data detected + } + + write_output_files(); if (writeback) { pool<Module*> wbmods; top->writeback(wbmods); } + delete fst; + } + + std::string cell_name(std::string const & name) + { + size_t pos = name.find_last_of("["); + if (pos!=std::string::npos) + return name.substr(0, pos); + return name; + } + + int mem_cell_addr(std::string const & name) + { + size_t pos = name.find_last_of("["); + return atoi(name.substr(pos+1).c_str()); + } + + void run_cosim_aiger_witness(Module *topmod) + { + log_assert(top == nullptr); + if (!multiclock && (clock.size()+clockn.size())==0) + log_error("Clock signal must be specified.\n"); + if (multiclock && (clock.size()+clockn.size())>0) + log_error("For multiclock witness there should be no clock signal.\n"); + + top = new SimInstance(this, scope, topmod); + register_signals(); + + std::ifstream mf(map_filename); + std::string type, symbol; + int variable, index; + dict<int, std::pair<SigBit,bool>> inputs, inits, latches; + dict<int, std::pair<std::string,int>> mem_inits, mem_latches; + if (mf.fail()) + log_cmd_error("Not able to read AIGER witness map file.\n"); + while (mf >> type >> variable >> index >> symbol) { + RTLIL::IdString escaped_s = RTLIL::escape_id(symbol); + Wire *w = topmod->wire(escaped_s); + if (!w) { + escaped_s = RTLIL::escape_id(cell_name(symbol)); + Cell *c = topmod->cell(escaped_s); + if (!c) + log_warning("Wire/cell %s not present in module %s\n",symbol.c_str(),log_id(topmod)); + + if (c->is_mem_cell()) { + std::string memid = c->parameters.at(ID::MEMID).decode_string(); + auto &state = top->mem_database[memid]; + + int offset = (mem_cell_addr(symbol) - state.mem->start_offset) * state.mem->width + index; + if (type == "init") + mem_inits[variable] = { memid, offset }; + else if (type == "latch") + mem_latches[variable] = { memid, offset }; + else + log_error("Map file addressing cell %s as type %s\n", symbol.c_str(), type.c_str()); + } else { + log_error("Cell %s in map file is not memory cell\n", symbol.c_str()); + } + } else { + if (index < w->start_offset || index > w->start_offset + w->width) + log_error("Index %d for wire %s is out of range\n", index, log_signal(w)); + if (type == "input") { + inputs[variable] = {SigBit(w,index-w->start_offset), false}; + } else if (type == "init") { + inits[variable] = {SigBit(w,index-w->start_offset), false}; + } else if (type == "latch") { + latches[variable] = {SigBit(w,index-w->start_offset), false}; + } else if (type == "invlatch") { + latches[variable] = {SigBit(w,index-w->start_offset), true}; + } + } + } + + std::ifstream f; + f.open(sim_filename.c_str()); + if (f.fail() || GetSize(sim_filename) == 0) + log_error("Can not open file `%s`\n", sim_filename.c_str()); + + int state = 0; + std::string status; + int cycle = 0; + + while (!f.eof()) + { + std::string line; + std::getline(f, line); + if (line.size()==0 || line[0]=='#' || line[0]=='c' || line[0]=='f' || line[0]=='u') continue; + if (line[0]=='.') break; + if (state==0 && line.size()!=1) { + // old format detected, latch data + state = 2; + } + if (state==1 && line[0]!='b' && line[0]!='j') { + // was old format but with 1 bit latch + top->setState(latches, status); + state = 3; + } + + switch(state) + { + case 0: + status = line; + state = 1; + break; + case 1: + state = 2; + break; + case 2: + top->setState(latches, line); + top->setMemState(mem_latches, line); + state = 3; + break; + default: + if (verbose) + log("Simulating cycle %d.\n", cycle); + top->setState(inputs, line); + if (cycle) { + set_inports(clock, State::S1); + set_inports(clockn, State::S0); + } else { + top->setState(inits, line); + top->setMemState(mem_inits, line); + set_inports(clock, State::S0); + set_inports(clockn, State::S1); + } + update(); + register_output_step(10*cycle); + if (!multiclock && cycle) { + set_inports(clock, State::S0); + set_inports(clockn, State::S1); + update(); + register_output_step(10*cycle + 5); + } + cycle++; + break; + } + } + register_output_step(10*cycle); + write_output_files(); + } + + std::vector<std::string> split(std::string text, const char *delim) + { + std::vector<std::string> list; + char *p = strdup(text.c_str()); + char *t = strtok(p, delim); + while (t != NULL) { + list.push_back(t); + t = strtok(NULL, delim); + } + free(p); + return list; + } + + std::string signal_name(std::string const & name) + { + size_t pos = name.find_first_of("@"); + if (pos==std::string::npos) { + pos = name.find_first_of("#"); + if (pos==std::string::npos) + log_error("Line does not contain proper signal name `%s`\n", name.c_str()); + } + return name.substr(0, pos); + } + + void run_cosim_btor2_witness(Module *topmod) + { + log_assert(top == nullptr); + if (!multiclock && (clock.size()+clockn.size())==0) + log_error("Clock signal must be specified.\n"); + if (multiclock && (clock.size()+clockn.size())>0) + log_error("For multiclock witness there should be no clock signal.\n"); + std::ifstream f; + f.open(sim_filename.c_str()); + if (f.fail() || GetSize(sim_filename) == 0) + log_error("Can not open file `%s`\n", sim_filename.c_str()); + + int state = 0; + int cycle = 0; + top = new SimInstance(this, scope, topmod); + register_signals(); + int prev_cycle = 0; + int curr_cycle = 0; + std::vector<std::string> parts; + size_t len = 0; + while (!f.eof()) + { + std::string line; + std::getline(f, line); + if (line.size()==0) continue; + + if (line[0]=='#' || line[0]=='@' || line[0]=='.') { + if (line[0]!='.') + curr_cycle = atoi(line.c_str()+1); + else + curr_cycle = -1; // force detect change + + if (curr_cycle != prev_cycle) { + if (verbose) + log("Simulating cycle %d.\n", cycle); + set_inports(clock, State::S1); + set_inports(clockn, State::S0); + update(); + register_output_step(10*cycle+0); + if (!multiclock) { + set_inports(clock, State::S0); + set_inports(clockn, State::S1); + update(); + register_output_step(10*cycle+5); + } + cycle++; + prev_cycle = curr_cycle; + } + if (line[0]=='.') break; + continue; + } + + switch(state) + { + case 0: + if (line=="sat") + state = 1; + break; + case 1: + if (line[0]=='b' || line[0]=='j') + state = 2; + else + log_error("Line does not contain property.\n"); + break; + default: // set state or inputs + parts = split(line, " "); + len = parts.size(); + if (len<3 || len>4) + log_error("Invalid set state line content.\n"); + + RTLIL::IdString escaped_s = RTLIL::escape_id(signal_name(parts[len-1])); + if (len==3) { + Wire *w = topmod->wire(escaped_s); + if (!w) { + Cell *c = topmod->cell(escaped_s); + if (!c) + log_warning("Wire/cell %s not present in module %s\n",log_id(escaped_s),log_id(topmod)); + else if (c->type.in(ID($anyconst), ID($anyseq))) { + SigSpec sig_y= c->getPort(ID::Y); + if ((int)parts[1].size() != GetSize(sig_y)) + log_error("Size of wire %s is different than provided data.\n", log_signal(sig_y)); + top->set_state(sig_y, Const::from_string(parts[1])); + } + } else { + if ((int)parts[1].size() != w->width) + log_error("Size of wire %s is different than provided data.\n", log_signal(w)); + top->set_state(w, Const::from_string(parts[1])); + } + } else { + Cell *c = topmod->cell(escaped_s); + if (!c) + log_error("Cell %s not present in module %s\n",log_id(escaped_s),log_id(topmod)); + if (!c->is_mem_cell()) + log_error("Cell %s is not memory cell in module %s\n",log_id(escaped_s),log_id(topmod)); + + Const addr = Const::from_string(parts[1].substr(1,parts[1].size()-2)); + Const data = Const::from_string(parts[2]); + top->set_memory_state(c->parameters.at(ID::MEMID).decode_string(), addr, data); + } + break; + } + } + register_output_step(10*cycle); + write_output_files(); + } + + std::string define_signal(Wire *wire) + { + std::stringstream f; + + if (wire->width==1) + f << stringf("%s", RTLIL::unescape_id(wire->name).c_str()); + else + if (wire->upto) + f << stringf("[%d:%d] %s", wire->start_offset, wire->width - 1 + wire->start_offset, RTLIL::unescape_id(wire->name).c_str()); + else + f << stringf("[%d:%d] %s", wire->width - 1 + wire->start_offset, wire->start_offset, RTLIL::unescape_id(wire->name).c_str()); + return f.str(); + } + + std::string signal_list(std::map<Wire*,fstHandle> &signals) + { + std::stringstream f; + for(auto item=signals.begin();item!=signals.end();item++) + f << stringf("%c%s", (item==signals.begin() ? ' ' : ','), RTLIL::unescape_id(item->first->name).c_str()); + return f.str(); + } + + void generate_tb(Module *topmod, std::string tb_filename, int numcycles) + { + fst = new FstData(sim_filename); + + if (scope.empty()) + log_error("Scope must be defined for co-simulation.\n"); + + if ((clock.size()+clockn.size())==0) + log_error("Clock signal must be specified.\n"); + + std::vector<fstHandle> fst_clock; + std::map<Wire*,fstHandle> clocks; + + for (auto portname : clock) + { + Wire *w = topmod->wire(portname); + if (!w) + log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module)); + if (!w->port_input) + log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module)); + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname)); + if (id==0) + log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname)); + fst_clock.push_back(id); + clocks[w] = id; + } + for (auto portname : clockn) + { + Wire *w = topmod->wire(portname); + if (!w) + log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module)); + if (!w->port_input) + log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module)); + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname)); + if (id==0) + log_error("Can't find port %s.%s in FST.\n", scope.c_str(), log_id(portname)); + fst_clock.push_back(id); + clocks[w] = id; + } + + SigMap sigmap(topmod); + std::map<Wire*,fstHandle> inputs; + std::map<Wire*,fstHandle> outputs; + + for (auto wire : topmod->wires()) { + fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name)); + if (id==0 && (wire->port_input || wire->port_output)) + log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)).c_str()); + if (wire->port_input) + if (clocks.find(wire)==clocks.end()) + inputs[wire] = id; + if (wire->port_output) + outputs[wire] = id; + } + + uint64_t startCount = 0; + uint64_t stopCount = 0; + if (start_time==0) { + if (start_time < fst->getStartTime()) + log_warning("Start time is before simulation file start time\n"); + startCount = fst->getStartTime(); + } else if (start_time==-1) + startCount = fst->getEndTime(); + else { + startCount = start_time / fst->getTimescale(); + if (startCount > fst->getEndTime()) { + startCount = fst->getEndTime(); + log_warning("Start time is after simulation file end time\n"); + } + } + if (stop_time==0) { + if (stop_time < fst->getStartTime()) + log_warning("Stop time is before simulation file start time\n"); + stopCount = fst->getStartTime(); + } else if (stop_time==-1) + stopCount = fst->getEndTime(); + else { + stopCount = stop_time / fst->getTimescale(); + if (stopCount > fst->getEndTime()) { + stopCount = fst->getEndTime(); + log_warning("Stop time is after simulation file end time\n"); + } + } + if (stopCount<startCount) { + log_error("Stop time is before start time\n"); + } + + int cycle = 0; + log("Generate testbench data from %lu%s to %lu%s", (unsigned long)startCount, fst->getTimescaleString(), (unsigned long)stopCount, fst->getTimescaleString()); + if (cycles_set) + log(" for %d clock cycle(s)",numcycles); + log("\n"); + + std::stringstream f; + f << stringf("`timescale 1%s/1%s\n", fst->getTimescaleString(),fst->getTimescaleString()); + f << stringf("module %s();\n",tb_filename.c_str()); + int clk_len = 0; + int inputs_len = 0; + int outputs_len = 0; + for(auto &item : clocks) { + clk_len += item.first->width; + f << "\treg " << define_signal(item.first) << ";\n"; + } + for(auto &item : inputs) { + inputs_len += item.first->width; + f << "\treg " << define_signal(item.first) << ";\n"; + } + for(auto &item : outputs) { + outputs_len += item.first->width; + f << "\twire " << define_signal(item.first) << ";\n"; + } + int data_len = clk_len + inputs_len + outputs_len + 32; + f << "\n"; + f << stringf("\t%s uut(",RTLIL::unescape_id(topmod->name).c_str()); + for(auto item=clocks.begin();item!=clocks.end();item++) + f << stringf("%c.%s(%s)", (item==clocks.begin() ? ' ' : ','), RTLIL::unescape_id(item->first->name).c_str(), RTLIL::unescape_id(item->first->name).c_str()); + for(auto &item : inputs) + f << stringf(",.%s(%s)", RTLIL::unescape_id(item.first->name).c_str(), RTLIL::unescape_id(item.first->name).c_str()); + for(auto &item : outputs) + f << stringf(",.%s(%s)", RTLIL::unescape_id(item.first->name).c_str(), RTLIL::unescape_id(item.first->name).c_str()); + f << ");\n"; + f << "\n"; + f << "\tinteger i;\n"; + uint64_t prev_time = startCount; + log("Writing data to `%s`\n", (tb_filename+".txt").c_str()); + std::ofstream data_file(tb_filename+".txt"); + std::stringstream initstate; + try { + fst->reconstructAllAtTimes(fst_clock, startCount, stopCount, [&](uint64_t time) { + for(auto &item : clocks) + data_file << stringf("%s",fst->valueOf(item.second).c_str()); + for(auto &item : inputs) + data_file << stringf("%s",fst->valueOf(item.second).c_str()); + for(auto &item : outputs) + data_file << stringf("%s",fst->valueOf(item.second).c_str()); + data_file << stringf("%s\n",Const(time-prev_time).as_string().c_str()); + + if (time==startCount) { + // initial state + for(auto var : fst->getVars()) { + if (var.is_reg && !Const::from_string(fst->valueOf(var.id).c_str()).is_fully_undef()) { + if (var.scope == scope) { + initstate << stringf("\t\tuut.%s = %d'b%s;\n", var.name.c_str(), var.width, fst->valueOf(var.id).c_str()); + } else if (var.scope.find(scope+".")==0) { + initstate << stringf("\t\tuut.%s.%s = %d'b%s;\n",var.scope.substr(scope.size()+1).c_str(), var.name.c_str(), var.width, fst->valueOf(var.id).c_str()); + } + } + } + } + cycle++; + prev_time = time; + + // Limit to number of cycles if provided + if (cycles_set && cycle > numcycles *2) + throw fst_end_of_data_exception(); + if (time==stopCount) + throw fst_end_of_data_exception(); + }); + } catch(fst_end_of_data_exception) { + // end of data detected + } + + f << stringf("\treg [0:%d] data [0:%d];\n", data_len-1, cycle-1); + f << "\tinitial begin;\n"; + f << stringf("\t\t$dumpfile(\"%s\");\n",tb_filename.c_str()); + f << stringf("\t\t$dumpvars(0,%s);\n",tb_filename.c_str()); + f << initstate.str(); + f << stringf("\t\t$readmemb(\"%s.txt\", data);\n",tb_filename.c_str()); + + f << stringf("\t\t#(data[0][%d:%d]);\n", data_len-32, data_len-1); + f << stringf("\t\t{%s } = data[0][%d:%d];\n", signal_list(clocks).c_str(), 0, clk_len-1); + f << stringf("\t\t{%s } <= data[0][%d:%d];\n", signal_list(inputs).c_str(), clk_len, clk_len+inputs_len-1); + + f << stringf("\t\tfor (i = 1; i < %d; i++) begin\n",cycle); + + f << stringf("\t\t\t#(data[i][%d:%d]);\n", data_len-32, data_len-1); + f << stringf("\t\t\t{%s } = data[i][%d:%d];\n", signal_list(clocks).c_str(), 0, clk_len-1); + f << stringf("\t\t\t{%s } <= data[i][%d:%d];\n", signal_list(inputs).c_str(), clk_len, clk_len+inputs_len-1); + + f << stringf("\t\t\tif ({%s } != data[i-1][%d:%d]) begin\n", signal_list(outputs).c_str(), clk_len+inputs_len, clk_len+inputs_len+outputs_len-1); + f << "\t\t\t\t$error(\"Signal difference detected\\n\");\n"; + f << "\t\t\tend\n"; + + f << "\t\tend\n"; + + f << "\t\t$finish;\n"; + f << "\tend\n"; + f << "endmodule\n"; + + log("Writing testbench to `%s`\n", (tb_filename+".v").c_str()); + std::ofstream tb_file(tb_filename+".v"); + tb_file << f.str(); + + delete fst; } }; +struct VCDWriter : public OutputWriter +{ + VCDWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) { + vcdfile.open(filename.c_str()); + } + + void write(std::map<int, bool> &use_signal) override + { + if (!vcdfile.is_open()) return; + vcdfile << stringf("$version %s $end\n", worker->date ? yosys_version_str : "Yosys"); + + if (worker->date) { + std::time_t t = std::time(nullptr); + char mbstr[255]; + if (std::strftime(mbstr, sizeof(mbstr), "%c", std::localtime(&t))) { + vcdfile << stringf("$date ") << mbstr << stringf(" $end\n"); + } + } + + if (!worker->timescale.empty()) + vcdfile << stringf("$timescale %s $end\n", worker->timescale.c_str()); + + worker->top->write_output_header( + [this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); }, + [this]() { vcdfile << stringf("$upscope $end\n");}, + [this,use_signal](Wire *wire, int id, bool is_reg) { if (use_signal.at(id)) vcdfile << stringf("$var %s %d n%d %s%s $end\n", is_reg ? "reg" : "wire", GetSize(wire), id, wire->name[0] == '$' ? "\\" : "", log_id(wire)); } + ); + + vcdfile << stringf("$enddefinitions $end\n"); + + for(auto& d : worker->output_data) + { + vcdfile << stringf("#%d\n", d.first); + for (auto &data : d.second) + { + if (!use_signal.at(data.first)) continue; + Const value = data.second; + vcdfile << "b"; + for (int i = GetSize(value)-1; i >= 0; i--) { + switch (value[i]) { + case State::S0: vcdfile << "0"; break; + case State::S1: vcdfile << "1"; break; + case State::Sx: vcdfile << "x"; break; + default: vcdfile << "z"; + } + } + vcdfile << stringf(" n%d\n", data.first); + } + } + } + + std::ofstream vcdfile; +}; + +struct FSTWriter : public OutputWriter +{ + FSTWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) { + fstfile = (struct fstContext *)fstWriterCreate(filename.c_str(),1); + } + + virtual ~FSTWriter() + { + fstWriterClose(fstfile); + } + + void write(std::map<int, bool> &use_signal) override + { + if (!fstfile) return; + std::time_t t = std::time(nullptr); + fstWriterSetVersion(fstfile, worker->date ? yosys_version_str : "Yosys"); + if (worker->date) + fstWriterSetDate(fstfile, asctime(std::localtime(&t))); + else + fstWriterSetDate(fstfile, ""); + if (!worker->timescale.empty()) + fstWriterSetTimescaleFromString(fstfile, worker->timescale.c_str()); + + fstWriterSetPackType(fstfile, FST_WR_PT_FASTLZ); + fstWriterSetRepackOnClose(fstfile, 1); + + worker->top->write_output_header( + [this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); }, + [this]() { fstWriterSetUpscope(fstfile); }, + [this,use_signal](Wire *wire, int id, bool is_reg) { + if (!use_signal.at(id)) return; + fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, GetSize(wire), + stringf("%s%s", wire->name[0] == '$' ? "\\" : "", log_id(wire)).c_str(), 0); + + mapping.emplace(id, fst_id); + } + ); + + for(auto& d : worker->output_data) + { + fstWriterEmitTimeChange(fstfile, d.first); + for (auto &data : d.second) + { + if (!use_signal.at(data.first)) continue; + Const value = data.second; + std::stringstream ss; + for (int i = GetSize(value)-1; i >= 0; i--) { + switch (value[i]) { + case State::S0: ss << "0"; break; + case State::S1: ss << "1"; break; + case State::Sx: ss << "x"; break; + default: ss << "z"; + } + } + fstWriterEmitValueChange(fstfile, mapping[data.first], ss.str().c_str()); + } + } + } + + struct fstContext *fstfile = nullptr; + std::map<int,fstHandle> mapping; +}; + +struct AIWWriter : public OutputWriter +{ + AIWWriter(SimWorker *worker, std::string filename) : OutputWriter(worker) { + aiwfile.open(filename.c_str()); + } + + virtual ~AIWWriter() + { + aiwfile << '.' << '\n'; + } + + void write(std::map<int, bool> &) override + { + if (!aiwfile.is_open()) return; + if (worker->map_filename.empty()) + log_cmd_error("For AIGER witness file map parameter is mandatory.\n"); + + std::ifstream mf(worker->map_filename); + std::string type, symbol; + int variable, index; + int max_input = 0; + if (mf.fail()) + log_cmd_error("Not able to read AIGER witness map file.\n"); + while (mf >> type >> variable >> index >> symbol) { + RTLIL::IdString escaped_s = RTLIL::escape_id(symbol); + Wire *w = worker->top->module->wire(escaped_s); + if (!w) + log_error("Wire %s not present in module %s\n",log_id(escaped_s),log_id(worker->top->module)); + if (index < w->start_offset || index > w->start_offset + w->width) + log_error("Index %d for wire %s is out of range\n", index, log_signal(w)); + if (type == "input") { + aiw_inputs[variable] = SigBit(w,index-w->start_offset); + if (worker->clock.count(escaped_s)) { + clocks[variable] = true; + } + if (worker->clockn.count(escaped_s)) { + clocks[variable] = false; + } + max_input = max(max_input,variable); + } else if (type == "init") { + aiw_inits[variable] = SigBit(w,index-w->start_offset); + max_input = max(max_input,variable); + } else if (type == "latch") { + aiw_latches[variable] = {SigBit(w,index-w->start_offset), false}; + } else if (type == "invlatch") { + aiw_latches[variable] = {SigBit(w,index-w->start_offset), true}; + } + } + + worker->top->write_output_header( + [](IdString) {}, + []() {}, + [this](Wire *wire, int id, bool) { mapping[wire] = id; } + ); + + std::map<int, Yosys::RTLIL::Const> current; + bool first = true; + for (auto iter = worker->output_data.begin(); iter != std::prev(worker->output_data.end()); ++iter) + { + auto& d = *iter; + for (auto &data : d.second) + { + current[data.first] = data.second; + } + if (first) { + for (int i = 0;; i++) + { + if (aiw_latches.count(i)) { + aiwfile << '0'; + continue; + } + aiwfile << '\n'; + break; + } + first = false; + } + + bool skip = false; + for (auto it : clocks) + { + auto val = it.second ? State::S1 : State::S0; + SigBit bit = aiw_inputs.at(it.first); + auto v = current[mapping[bit.wire]].bits.at(bit.offset); + if (v == val) + skip = true; + } + if (skip) + continue; + for (int i = 0; i <= max_input; i++) + { + if (aiw_inputs.count(i)) { + SigBit bit = aiw_inputs.at(i); + auto v = current[mapping[bit.wire]].bits.at(bit.offset); + if (v == State::S1) + aiwfile << '1'; + else + aiwfile << '0'; + continue; + } + if (aiw_inits.count(i)) { + SigBit bit = aiw_inits.at(i); + auto v = current[mapping[bit.wire]].bits.at(bit.offset); + if (v == State::S1) + aiwfile << '1'; + else + aiwfile << '0'; + continue; + } + aiwfile << '0'; + } + aiwfile << '\n'; + } + } + + std::ofstream aiwfile; + dict<int, std::pair<SigBit, bool>> aiw_latches; + dict<int, SigBit> aiw_inputs, aiw_inits; + dict<int, bool> clocks; + std::map<Wire*,int> mapping; +}; + struct SimPass : public Pass { SimPass() : Pass("sim", "simulate the circuit") { } void help() override @@ -767,12 +1928,28 @@ struct SimPass : public Pass { log(" -vcd <filename>\n"); log(" write the simulation results to the given VCD file\n"); log("\n"); + log(" -fst <filename>\n"); + log(" write the simulation results to the given FST file\n"); + log("\n"); + log(" -aiw <filename>\n"); + log(" write the simulation results to an AIGER witness file\n"); + log(" (requires a *.aim file via -map)\n"); + log("\n"); + log(" -x\n"); + log(" ignore constant x outputs in simulation file.\n"); + log("\n"); + log(" -date\n"); + log(" include date and full version info in output.\n"); + log("\n"); log(" -clock <portname>\n"); log(" name of top-level clock input\n"); log("\n"); log(" -clockn <portname>\n"); log(" name of top-level clock input (inverse polarity)\n"); log("\n"); + log(" -multiclock\n"); + log(" mark that witness file is multiclock.\n"); + log("\n"); log(" -reset <portname>\n"); log(" name of top-level reset input (active high)\n"); log("\n"); @@ -789,22 +1966,64 @@ struct SimPass : public Pass { log(" include the specified timescale declaration in the vcd\n"); log("\n"); log(" -n <integer>\n"); - log(" number of cycles to simulate (default: 20)\n"); + log(" number of clock cycles to simulate (default: 20)\n"); log("\n"); log(" -a\n"); - log(" include all nets in VCD output, not just those with public names\n"); + log(" use all nets in VCD/FST operations, not just those with public names\n"); log("\n"); log(" -w\n"); log(" writeback mode: use final simulation state as new init state\n"); log("\n"); + log(" -r\n"); + log(" read simulation results file (file formats supported: FST, VCD, AIW and WIT)\n"); + log(" VCD support requires vcd2fst external tool to be present\n"); + log("\n"); + log(" -map <filename>\n"); + log(" read file with port and latch symbols, needed for AIGER witness input\n"); + log("\n"); + log(" -scope <name>\n"); + log(" scope of simulation top model\n"); + log("\n"); + log(" -at <time>\n"); + log(" sets start and stop time\n"); + log("\n"); + log(" -start <time>\n"); + log(" start co-simulation in arbitary time (default 0)\n"); + log("\n"); + log(" -stop <time>\n"); + log(" stop co-simulation in arbitary time (default END)\n"); + log("\n"); + log(" -sim\n"); + log(" simulation with stimulus from FST (default)\n"); + log("\n"); + log(" -sim-cmp\n"); + log(" co-simulation expect exact match\n"); + log("\n"); + log(" -sim-gold\n"); + log(" co-simulation, x in simulation can match any value in FST\n"); + log("\n"); + log(" -sim-gate\n"); + log(" co-simulation, x in FST can match any value in simulation\n"); + log("\n"); + log(" -q\n"); + log(" disable per-cycle/sample log message\n"); + log("\n"); log(" -d\n"); log(" enable debug output\n"); log("\n"); } + + + static std::string file_base_name(std::string const & path) + { + return path.substr(path.find_last_of("/\\") + 1); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override { SimWorker worker; int numcycles = 20; + bool start_set = false, stop_set = false, at_set = false; log_header(design, "Executing SIM pass (simulate the circuit).\n"); @@ -813,11 +2032,24 @@ struct SimPass : public Pass { if (args[argidx] == "-vcd" && argidx+1 < args.size()) { std::string vcd_filename = args[++argidx]; rewrite_filename(vcd_filename); - worker.vcdfile.open(vcd_filename.c_str()); + worker.outputfiles.emplace_back(std::unique_ptr<VCDWriter>(new VCDWriter(&worker, vcd_filename.c_str()))); + continue; + } + if (args[argidx] == "-fst" && argidx+1 < args.size()) { + std::string fst_filename = args[++argidx]; + rewrite_filename(fst_filename); + worker.outputfiles.emplace_back(std::unique_ptr<FSTWriter>(new FSTWriter(&worker, fst_filename.c_str()))); + continue; + } + if (args[argidx] == "-aiw" && argidx+1 < args.size()) { + std::string aiw_filename = args[++argidx]; + rewrite_filename(aiw_filename); + worker.outputfiles.emplace_back(std::unique_ptr<AIWWriter>(new AIWWriter(&worker, aiw_filename.c_str()))); continue; } if (args[argidx] == "-n" && argidx+1 < args.size()) { numcycles = atoi(args[++argidx].c_str()); + worker.cycles_set = true; continue; } if (args[argidx] == "-rstlen" && argidx+1 < args.size()) { @@ -848,6 +2080,10 @@ struct SimPass : public Pass { worker.hide_internal = false; continue; } + if (args[argidx] == "-q") { + worker.verbose = false; + continue; + } if (args[argidx] == "-d") { worker.debug = true; continue; @@ -860,9 +2096,73 @@ struct SimPass : public Pass { worker.zinit = true; continue; } + if (args[argidx] == "-r" && argidx+1 < args.size()) { + std::string sim_filename = args[++argidx]; + rewrite_filename(sim_filename); + worker.sim_filename = sim_filename; + continue; + } + if (args[argidx] == "-map" && argidx+1 < args.size()) { + std::string map_filename = args[++argidx]; + rewrite_filename(map_filename); + worker.map_filename = map_filename; + continue; + } + if (args[argidx] == "-scope" && argidx+1 < args.size()) { + worker.scope = args[++argidx]; + continue; + } + if (args[argidx] == "-start" && argidx+1 < args.size()) { + worker.start_time = stringToTime(args[++argidx]); + start_set = true; + continue; + } + if (args[argidx] == "-stop" && argidx+1 < args.size()) { + worker.stop_time = stringToTime(args[++argidx]); + stop_set = true; + continue; + } + if (args[argidx] == "-at" && argidx+1 < args.size()) { + worker.start_time = stringToTime(args[++argidx]); + worker.stop_time = worker.start_time; + at_set = true; + continue; + } + if (args[argidx] == "-sim") { + worker.sim_mode = SimulationMode::sim; + continue; + } + if (args[argidx] == "-sim-cmp") { + worker.sim_mode = SimulationMode::cmp; + continue; + } + if (args[argidx] == "-sim-gold") { + worker.sim_mode = SimulationMode::gold; + continue; + } + if (args[argidx] == "-sim-gate") { + worker.sim_mode = SimulationMode::gate; + continue; + } + if (args[argidx] == "-x") { + worker.ignore_x = true; + continue; + } + if (args[argidx] == "-date") { + worker.date = true; + continue; + } + if (args[argidx] == "-multiclock") { + worker.multiclock = true; + continue; + } break; } extra_args(args, argidx, design); + if (at_set && (start_set || stop_set || worker.cycles_set)) + log_error("'at' option can only be defined separate of 'start','stop' and 'n'\n"); + if (stop_set && worker.cycles_set) + log_error("'stop' and 'n' can only be used exclusively'\n"); Module *top_mod = nullptr; @@ -878,8 +2178,139 @@ struct SimPass : public Pass { top_mod = mods.front(); } - worker.run(top_mod, numcycles); + if (worker.sim_filename.empty()) + worker.run(top_mod, numcycles); + else { + std::string filename_trim = file_base_name(worker.sim_filename); + if (filename_trim.size() > 4 && ((filename_trim.compare(filename_trim.size()-4, std::string::npos, ".fst") == 0) || + filename_trim.compare(filename_trim.size()-4, std::string::npos, ".vcd") == 0)) { + worker.run_cosim_fst(top_mod, numcycles); + } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".aiw") == 0) { + if (worker.map_filename.empty()) + log_cmd_error("For AIGER witness file map parameter is mandatory.\n"); + worker.run_cosim_aiger_witness(top_mod); + } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) { + worker.run_cosim_btor2_witness(top_mod); + } else { + log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str()); + } + } } } SimPass; +struct Fst2TbPass : public Pass { + Fst2TbPass() : Pass("fst2tb", "generate testbench out of fst file") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" fst2tb [options] [top-level]\n"); + log("\n"); + log("This command generates testbench for the circuit using the given top-level module\n"); + log("and simulus signal from FST file\n"); + log("\n"); + log(" -tb <name>\n"); + log(" generated testbench name.\n"); + log(" files <name>.v and <name>.txt are created as result.\n"); + log("\n"); + log(" -r <filename>\n"); + log(" read simulation FST file\n"); + log("\n"); + log(" -clock <portname>\n"); + log(" name of top-level clock input\n"); + log("\n"); + log(" -clockn <portname>\n"); + log(" name of top-level clock input (inverse polarity)\n"); + log("\n"); + log(" -scope <name>\n"); + log(" scope of simulation top model\n"); + log("\n"); + log(" -start <time>\n"); + log(" start co-simulation in arbitary time (default 0)\n"); + log("\n"); + log(" -stop <time>\n"); + log(" stop co-simulation in arbitary time (default END)\n"); + log("\n"); + log(" -n <integer>\n"); + log(" number of clock cycles to simulate (default: 20)\n"); + log("\n"); + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + SimWorker worker; + int numcycles = 20; + bool stop_set = false; + std::string tb_filename; + + log_header(design, "Executing FST2FB pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-clock" && argidx+1 < args.size()) { + worker.clock.insert(RTLIL::escape_id(args[++argidx])); + continue; + } + if (args[argidx] == "-clockn" && argidx+1 < args.size()) { + worker.clockn.insert(RTLIL::escape_id(args[++argidx])); + continue; + } + if (args[argidx] == "-r" && argidx+1 < args.size()) { + std::string sim_filename = args[++argidx]; + rewrite_filename(sim_filename); + worker.sim_filename = sim_filename; + continue; + } + if (args[argidx] == "-n" && argidx+1 < args.size()) { + numcycles = atoi(args[++argidx].c_str()); + worker.cycles_set = true; + continue; + } + if (args[argidx] == "-scope" && argidx+1 < args.size()) { + worker.scope = args[++argidx]; + continue; + } + if (args[argidx] == "-start" && argidx+1 < args.size()) { + worker.start_time = stringToTime(args[++argidx]); + continue; + } + if (args[argidx] == "-stop" && argidx+1 < args.size()) { + worker.stop_time = stringToTime(args[++argidx]); + stop_set = true; + continue; + } + if (args[argidx] == "-tb" && argidx+1 < args.size()) { + tb_filename = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + if (stop_set && worker.cycles_set) + log_error("'stop' and 'n' can only be used exclusively'\n"); + + Module *top_mod = nullptr; + + if (design->full_selection()) { + top_mod = design->top_module(); + + if (!top_mod) + log_cmd_error("Design has no top module, use the 'hierarchy' command to specify one.\n"); + } else { + auto mods = design->selected_whole_modules(); + if (GetSize(mods) != 1) + log_cmd_error("Only one top module must be selected.\n"); + top_mod = mods.front(); + } + + if (tb_filename.empty()) + log_cmd_error("Testbench name must be defined.\n"); + + if (worker.sim_filename.empty()) + log_cmd_error("Stimulus FST file must be defined.\n"); + + worker.generate_tb(top_mod, tb_filename, numcycles); + } +} Fst2TbPass; + PRIVATE_NAMESPACE_END diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 035699603..98ccfc303 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -29,6 +29,8 @@ OBJS += passes/techmap/extract_reduce.o OBJS += passes/techmap/alumacc.o OBJS += passes/techmap/dffinit.o OBJS += passes/techmap/pmuxtree.o +OBJS += passes/techmap/bmuxmap.o +OBJS += passes/techmap/demuxmap.o OBJS += passes/techmap/muxcover.o OBJS += passes/techmap/aigmap.o OBJS += passes/techmap/tribuf.o diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 49a0fad77..ff98a6e36 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -45,6 +45,7 @@ #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/ffinit.h" +#include "kernel/ff.h" #include "kernel/cost.h" #include "kernel/log.h" #include <stdlib.h> @@ -73,6 +74,8 @@ PRIVATE_NAMESPACE_BEGIN enum class gate_type_t { G_NONE, G_FF, + G_FF0, + G_FF1, G_BUF, G_NOT, G_AND, @@ -112,13 +115,14 @@ int map_autoidx; SigMap assign_map; RTLIL::Module *module; std::vector<gate_t> signal_list; -std::map<RTLIL::SigBit, int> signal_map; +dict<RTLIL::SigBit, int> signal_map; FfInitVals initvals; pool<std::string> enabled_gates; -bool recover_init, cmos_cost; +bool cmos_cost; +bool had_init; -bool clk_polarity, en_polarity; -RTLIL::SigSpec clk_sig, en_sig; +bool clk_polarity, en_polarity, arst_polarity, srst_polarity; +RTLIL::SigSpec clk_sig, en_sig, arst_sig, srst_sig; dict<int, std::string> pi_map, po_map; int map_signal(RTLIL::SigBit bit, gate_type_t gate_type = G(NONE), int in1 = -1, int in2 = -1, int in3 = -1, int in4 = -1) @@ -165,46 +169,84 @@ void mark_port(RTLIL::SigSpec sig) void extract_cell(RTLIL::Cell *cell, bool keepff) { - if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_))) - { - if (clk_polarity != (cell->type == ID($_DFF_P_))) + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff(&initvals, cell); + gate_type_t type = G(FF); + if (!ff.has_clk) return; - if (clk_sig != assign_map(cell->getPort(ID::C))) + if (ff.has_gclk) return; - if (GetSize(en_sig) != 0) + if (ff.has_aload) return; - goto matching_dff; - } - - if (cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) - { - if (clk_polarity != cell->type.in(ID($_DFFE_PN_), ID($_DFFE_PP_))) + if (ff.has_sr) return; - if (en_polarity != cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_))) + if (!ff.is_fine) return; - if (clk_sig != assign_map(cell->getPort(ID::C))) + if (clk_polarity != ff.pol_clk) return; - if (en_sig != assign_map(cell->getPort(ID::E))) + if (clk_sig != assign_map(ff.sig_clk)) return; - goto matching_dff; - } - - if (0) { - matching_dff: - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); + if (ff.has_ce) { + if (en_polarity != ff.pol_ce) + return; + if (en_sig != assign_map(ff.sig_ce)) + return; + } else { + if (GetSize(en_sig) != 0) + return; + } + if (ff.val_init == State::S1) { + type = G(FF1); + had_init = true; + } else if (ff.val_init == State::S0) { + type = G(FF0); + had_init = true; + } + if (ff.has_arst) { + if (arst_polarity != ff.pol_arst) + return; + if (arst_sig != assign_map(ff.sig_arst)) + return; + if (ff.val_arst == State::S1) { + if (type == G(FF0)) + return; + type = G(FF1); + } else if (ff.val_arst == State::S0) { + if (type == G(FF1)) + return; + type = G(FF0); + } + } else { + if (GetSize(arst_sig) != 0) + return; + } + if (ff.has_srst) { + if (srst_polarity != ff.pol_srst) + return; + if (srst_sig != assign_map(ff.sig_srst)) + return; + if (ff.val_srst == State::S1) { + if (type == G(FF0)) + return; + type = G(FF1); + } else if (ff.val_srst == State::S0) { + if (type == G(FF1)) + return; + type = G(FF0); + } + } else { + if (GetSize(srst_sig) != 0) + return; + } if (keepff) - for (auto &c : sig_q.chunks()) + for (auto &c : ff.sig_q.chunks()) if (c.wire != nullptr) c.wire->attributes[ID::keep] = 1; - assign_map.apply(sig_d); - assign_map.apply(sig_q); - - map_signal(sig_q, G(FF), map_signal(sig_d)); + map_signal(ff.sig_q, type, map_signal(ff.sig_d)); - module->remove(cell); + ff.remove(); return; } @@ -367,7 +409,7 @@ std::string remap_name(RTLIL::IdString abc_name, RTLIL::Wire **orig_wire = nullp return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1); } -void dump_loop_graph(FILE *f, int &nr, std::map<int, std::set<int>> &edges, std::set<int> &workpool, std::vector<int> &in_counts) +void dump_loop_graph(FILE *f, int &nr, dict<int, pool<int>> &edges, pool<int> &workpool, std::vector<int> &in_counts) { if (f == nullptr) return; @@ -378,7 +420,7 @@ void dump_loop_graph(FILE *f, int &nr, std::map<int, std::set<int>> &edges, std: fprintf(f, " label=\"slide%d\";\n", nr); fprintf(f, " rankdir=\"TD\";\n"); - std::set<int> nodes; + pool<int> nodes; for (auto &e : edges) { nodes.insert(e.first); for (auto n : e.second) @@ -401,9 +443,9 @@ void handle_loops() // http://en.wikipedia.org/wiki/Topological_sorting // (Kahn, Arthur B. (1962), "Topological sorting of large networks") - std::map<int, std::set<int>> edges; + dict<int, pool<int>> edges; std::vector<int> in_edges_count(signal_list.size()); - std::set<int> workpool; + pool<int> workpool; FILE *dot_f = nullptr; int dot_nr = 0; @@ -412,7 +454,7 @@ void handle_loops() // dot_f = fopen("test.dot", "w"); for (auto &g : signal_list) { - if (g.type == G(NONE) || g.type == G(FF)) { + if (g.type == G(NONE) || g.type == G(FF) || g.type == G(FF0) || g.type == G(FF1)) { workpool.insert(g.id); } else { if (g.in1 >= 0) { @@ -655,8 +697,9 @@ struct abc_output_filter }; void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::string script_file, std::string exe_file, - std::vector<std::string> &liberty_files, std::string constr_file, bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str, - bool keepff, std::string delay_target, std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode, + std::vector<std::string> &liberty_files, std::vector<std::string> &genlib_files, std::string constr_file, + bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str, bool keepff, std::string delay_target, + std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode, const std::vector<RTLIL::Cell*> &cells, bool show_tempdir, bool sop_mode, bool abc_dress) { module = current_module; @@ -666,7 +709,6 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin signal_list.clear(); pi_map.clear(); po_map.clear(); - recover_init = false; if (clk_str != "$") { @@ -675,14 +717,41 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin en_polarity = true; en_sig = RTLIL::SigSpec(); + + arst_polarity = true; + arst_sig = RTLIL::SigSpec(); + + srst_polarity = true; + srst_sig = RTLIL::SigSpec(); } if (!clk_str.empty() && clk_str != "$") { + std::string en_str; + std::string arst_str; + std::string srst_str; if (clk_str.find(',') != std::string::npos) { int pos = clk_str.find(','); - std::string en_str = clk_str.substr(pos+1); + en_str = clk_str.substr(pos+1); clk_str = clk_str.substr(0, pos); + } + if (en_str.find(',') != std::string::npos) { + int pos = en_str.find(','); + arst_str = en_str.substr(pos+1); + arst_str = en_str.substr(0, pos); + } + if (arst_str.find(',') != std::string::npos) { + int pos = arst_str.find(','); + srst_str = arst_str.substr(pos+1); + srst_str = arst_str.substr(0, pos); + } + if (clk_str[0] == '!') { + clk_polarity = false; + clk_str = clk_str.substr(1); + } + if (module->wire(RTLIL::escape_id(clk_str)) != nullptr) + clk_sig = assign_map(module->wire(RTLIL::escape_id(clk_str))); + if (en_str != "") { if (en_str[0] == '!') { en_polarity = false; en_str = en_str.substr(1); @@ -690,12 +759,22 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (module->wire(RTLIL::escape_id(en_str)) != nullptr) en_sig = assign_map(module->wire(RTLIL::escape_id(en_str))); } - if (clk_str[0] == '!') { - clk_polarity = false; - clk_str = clk_str.substr(1); + if (arst_str != "") { + if (arst_str[0] == '!') { + arst_polarity = false; + arst_str = arst_str.substr(1); + } + if (module->wire(RTLIL::escape_id(arst_str)) != nullptr) + arst_sig = assign_map(module->wire(RTLIL::escape_id(arst_str))); + } + if (srst_str != "") { + if (srst_str[0] == '!') { + srst_polarity = false; + srst_str = srst_str.substr(1); + } + if (module->wire(RTLIL::escape_id(srst_str)) != nullptr) + srst_sig = assign_map(module->wire(RTLIL::escape_id(srst_str))); } - if (module->wire(RTLIL::escape_id(clk_str)) != nullptr) - clk_sig = assign_map(module->wire(RTLIL::escape_id(clk_str))); } if (dff_mode && clk_sig.empty()) @@ -710,8 +789,11 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin std::string abc_script = stringf("read_blif %s/input.blif; ", tempdir_name.c_str()); - if (!liberty_files.empty()) { - for (std::string liberty_file : liberty_files) abc_script += stringf("read_lib -w %s; ", liberty_file.c_str()); + if (!liberty_files.empty() || !genlib_files.empty()) { + for (std::string liberty_file : liberty_files) + abc_script += stringf("read_lib -w %s; ", liberty_file.c_str()); + for (std::string liberty_file : genlib_files) + abc_script += stringf("read_library %s; ", liberty_file.c_str()); if (!constr_file.empty()) abc_script += stringf("read_constr -v %s; ", constr_file.c_str()); } else @@ -739,7 +821,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin abc_script += fast_mode ? ABC_FAST_COMMAND_LUT : ABC_COMMAND_LUT; if (all_luts_cost_same && !fast_mode) abc_script += "; lutpack {S}"; - } else if (!liberty_files.empty()) + } else if (!liberty_files.empty() || !genlib_files.empty()) abc_script += constr_file.empty() ? (fast_mode ? ABC_FAST_COMMAND_LIB : ABC_COMMAND_LIB) : (fast_mode ? ABC_FAST_COMMAND_CTR : ABC_COMMAND_CTR); else if (sop_mode) abc_script += fast_mode ? ABC_FAST_COMMAND_SOP : ABC_COMMAND_SOP; @@ -753,10 +835,10 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (size_t pos = abc_script.find("{D}"); pos != std::string::npos; pos = abc_script.find("{D}", pos)) abc_script = abc_script.substr(0, pos) + delay_target + abc_script.substr(pos+3); - for (size_t pos = abc_script.find("{I}"); pos != std::string::npos; pos = abc_script.find("{D}", pos)) + for (size_t pos = abc_script.find("{I}"); pos != std::string::npos; pos = abc_script.find("{I}", pos)) abc_script = abc_script.substr(0, pos) + sop_inputs + abc_script.substr(pos+3); - for (size_t pos = abc_script.find("{P}"); pos != std::string::npos; pos = abc_script.find("{D}", pos)) + for (size_t pos = abc_script.find("{P}"); pos != std::string::npos; pos = abc_script.find("{P}", pos)) abc_script = abc_script.substr(0, pos) + sop_products + abc_script.substr(pos+3); for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos)) @@ -785,10 +867,15 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin log("Found%s %s clock domain: %s", clk_str.empty() ? "" : " matching", clk_polarity ? "posedge" : "negedge", log_signal(clk_sig)); if (en_sig.size() != 0) log(", enabled by %s%s", en_polarity ? "" : "!", log_signal(en_sig)); + if (arst_sig.size() != 0) + log(", asynchronously reset by %s%s", arst_polarity ? "" : "!", log_signal(arst_sig)); + if (srst_sig.size() != 0) + log(", synchronously reset by %s%s", srst_polarity ? "" : "!", log_signal(srst_sig)); log("\n"); } } + had_init = false; for (auto c : cells) extract_cell(c, keepff); @@ -807,6 +894,12 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (en_sig.size() != 0) mark_port(en_sig); + if (arst_sig.size() != 0) + mark_port(arst_sig); + + if (srst_sig.size() != 0) + mark_port(srst_sig); + handle_loops(); buffer = stringf("%s/input.blif", tempdir_name.c_str()); @@ -913,11 +1006,11 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin fprintf(f, "00-- 1\n"); fprintf(f, "--00 1\n"); } else if (si.type == G(FF)) { - if (si.init == State::S0 || si.init == State::S1) { - fprintf(f, ".latch ys__n%d ys__n%d %d\n", si.in1, si.id, si.init == State::S1 ? 1 : 0); - recover_init = true; - } else - fprintf(f, ".latch ys__n%d ys__n%d 2\n", si.in1, si.id); + fprintf(f, ".latch ys__n%d ys__n%d 2\n", si.in1, si.id); + } else if (si.type == G(FF0)) { + fprintf(f, ".latch ys__n%d ys__n%d 0\n", si.in1, si.id); + } else if (si.type == G(FF1)) { + fprintf(f, ".latch ys__n%d ys__n%d 1\n", si.in1, si.id); } else if (si.type != G(NONE)) log_abort(); if (si.type != G(NONE)) @@ -1020,7 +1113,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (ifs.fail()) log_error("Can't open ABC output file `%s'.\n", buffer.c_str()); - bool builtin_lib = liberty_files.empty(); + bool builtin_lib = liberty_files.empty() && genlib_files.empty(); RTLIL::Design *mapped_design = new RTLIL::Design; parse_blif(mapped_design, ifs, builtin_lib ? ID(DFF) : ID(_dff_), false, sop_mode); @@ -1039,7 +1132,10 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin design->select(module, wire); } - std::map<std::string, int> cell_stats; + SigMap mapped_sigmap(mapped_mod); + FfInitVals mapped_initvals(&mapped_sigmap, mapped_mod); + + dict<std::string, int> cell_stats; for (auto c : mapped_mod->cells()) { if (builtin_lib) @@ -1145,20 +1241,41 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin } if (c->type == ID(DFF)) { log_assert(clk_sig.size() == 1); - RTLIL::Cell *cell; - if (en_sig.size() == 0) { - cell = module->addCell(remap_name(c->name), clk_polarity ? ID($_DFF_P_) : ID($_DFF_N_)); - } else { + FfData ff(module, &initvals, remap_name(c->name)); + ff.width = 1; + ff.is_fine = true; + ff.has_clk = true; + ff.pol_clk = clk_polarity; + ff.sig_clk = clk_sig; + if (en_sig.size() != 0) { log_assert(en_sig.size() == 1); - cell = module->addCell(remap_name(c->name), stringf("$_DFFE_%c%c_", clk_polarity ? 'P' : 'N', en_polarity ? 'P' : 'N')); - cell->setPort(ID::E, en_sig); + ff.has_ce = true; + ff.pol_ce = en_polarity; + ff.sig_ce = en_sig; } - if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx; - for (auto name : {ID::D, ID::Q}) { - RTLIL::IdString remapped_name = remap_name(c->getPort(name).as_wire()->name); - cell->setPort(name, module->wire(remapped_name)); + RTLIL::Const init = mapped_initvals(c->getPort(ID::Q)); + if (had_init) + ff.val_init = init; + else + ff.val_init = State::Sx; + if (arst_sig.size() != 0) { + log_assert(arst_sig.size() == 1); + ff.has_arst = true; + ff.pol_arst = arst_polarity; + ff.sig_arst = arst_sig; + ff.val_arst = init; } - cell->setPort(ID::C, clk_sig); + if (srst_sig.size() != 0) { + log_assert(srst_sig.size() == 1); + ff.has_srst = true; + ff.pol_srst = srst_polarity; + ff.sig_srst = srst_sig; + ff.val_srst = init; + } + ff.sig_d = module->wire(remap_name(c->getPort(ID::D).as_wire()->name)); + ff.sig_q = module->wire(remap_name(c->getPort(ID::Q).as_wire()->name)); + RTLIL::Cell *cell = ff.emit(); + if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx; design->select(module, cell); continue; } @@ -1176,20 +1293,38 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (c->type == ID(_dff_)) { log_assert(clk_sig.size() == 1); - RTLIL::Cell *cell; - if (en_sig.size() == 0) { - cell = module->addCell(remap_name(c->name), clk_polarity ? ID($_DFF_P_) : ID($_DFF_N_)); - } else { + FfData ff(module, &initvals, remap_name(c->name)); + ff.width = 1; + ff.is_fine = true; + ff.has_clk = true; + ff.pol_clk = clk_polarity; + ff.sig_clk = clk_sig; + if (en_sig.size() != 0) { log_assert(en_sig.size() == 1); - cell = module->addCell(remap_name(c->name), stringf("$_DFFE_%c%c_", clk_polarity ? 'P' : 'N', en_polarity ? 'P' : 'N')); - cell->setPort(ID::E, en_sig); + ff.pol_ce = en_polarity; + ff.sig_ce = en_sig; } - if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx; - for (auto name : {ID::D, ID::Q}) { - RTLIL::IdString remapped_name = remap_name(c->getPort(name).as_wire()->name); - cell->setPort(name, module->wire(remapped_name)); + RTLIL::Const init = mapped_initvals(c->getPort(ID::Q)); + if (had_init) + ff.val_init = init; + else + ff.val_init = State::Sx; + if (arst_sig.size() != 0) { + log_assert(arst_sig.size() == 1); + ff.pol_arst = arst_polarity; + ff.sig_arst = arst_sig; + ff.val_arst = init; } - cell->setPort(ID::C, clk_sig); + if (srst_sig.size() != 0) { + log_assert(srst_sig.size() == 1); + ff.pol_srst = srst_polarity; + ff.sig_srst = srst_sig; + ff.val_srst = init; + } + ff.sig_d = module->wire(remap_name(c->getPort(ID::D).as_wire()->name)); + ff.sig_q = module->wire(remap_name(c->getPort(ID::Q).as_wire()->name)); + RTLIL::Cell *cell = ff.emit(); + if (markgroups) cell->attributes[ID::abcgroup] = map_autoidx; design->select(module, cell); continue; } @@ -1225,15 +1360,6 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin module->connect(conn); } - if (recover_init) - for (auto wire : mapped_mod->wires()) { - if (wire->attributes.count(ID::init)) { - Wire *w = module->wire(remap_name(wire->name)); - log_assert(w->attributes.count(ID::init) == 0); - w->attributes[ID::init] = wire->attributes.at(ID::init); - } - } - for (auto &it : cell_stats) log("ABC RESULTS: %15s cells: %8d\n", it.first.c_str(), it.second); int in_wires = 0, out_wires = 0; @@ -1302,10 +1428,10 @@ struct AbcPass : public Pass { log("\n"); log(" if no -script parameter is given, the following scripts are used:\n"); log("\n"); - log(" for -liberty without -constr:\n"); + log(" for -liberty/-genlib without -constr:\n"); log("%s\n", fold_abc_cmd(ABC_COMMAND_LIB).c_str()); log("\n"); - log(" for -liberty with -constr:\n"); + log(" for -liberty/-genlib with -constr:\n"); log("%s\n", fold_abc_cmd(ABC_COMMAND_CTR).c_str()); log("\n"); log(" for -lut/-luts (only one LUT size):\n"); @@ -1324,10 +1450,10 @@ struct AbcPass : public Pass { log(" use different default scripts that are slightly faster (at the cost\n"); log(" of output quality):\n"); log("\n"); - log(" for -liberty without -constr:\n"); + log(" for -liberty/-genlib without -constr:\n"); log("%s\n", fold_abc_cmd(ABC_FAST_COMMAND_LIB).c_str()); log("\n"); - log(" for -liberty with -constr:\n"); + log(" for -liberty/-genlib with -constr:\n"); log("%s\n", fold_abc_cmd(ABC_FAST_COMMAND_CTR).c_str()); log("\n"); log(" for -lut/-luts:\n"); @@ -1343,8 +1469,13 @@ struct AbcPass : public Pass { log(" generate netlists for the specified cell library (using the liberty\n"); log(" file format).\n"); log("\n"); + log(" -genlib <file>\n"); + log(" generate netlists for the specified cell library (using the SIS Genlib\n"); + log(" file format).\n"); + log("\n"); log(" -constr <file>\n"); - log(" pass this file with timing constraints to ABC. use with -liberty.\n"); + log(" pass this file with timing constraints to ABC.\n"); + log(" use with -liberty/-genlib.\n"); log("\n"); log(" a constr file contains two lines:\n"); log(" set_driving_cell <cell_name>\n"); @@ -1390,7 +1521,7 @@ struct AbcPass : public Pass { log("\n"); // log(" -mux4, -mux8, -mux16\n"); // log(" try to extract 4-input, 8-input, and/or 16-input muxes\n"); - // log(" (ignored when used with -liberty or -lut)\n"); + // log(" (ignored when used with -liberty/-genlib or -lut)\n"); // log("\n"); log(" -g type1,type2,...\n"); log(" Map to the specified list of gate types. Supported gates types are:\n"); @@ -1446,7 +1577,7 @@ struct AbcPass : public Pass { log(" preserve naming by an equivalence check between the original and post-ABC\n"); log(" netlists (experimental).\n"); log("\n"); - log("When neither -liberty nor -lut is used, the Yosys standard cell library is\n"); + log("When no target cell library is specified the Yosys standard cell library is\n"); log("loaded into ABC before the ABC script is executed.\n"); log("\n"); log("Note that this is a logic optimization pass within Yosys that is calling ABC\n"); @@ -1473,7 +1604,7 @@ struct AbcPass : public Pass { std::string exe_file = yosys_abc_executable; std::string script_file, default_liberty_file, constr_file, clk_str; - std::vector<std::string> liberty_files; + std::vector<std::string> liberty_files, genlib_files; std::string delay_target, sop_inputs, sop_products, lutin_shared = "-S 1"; bool fast_mode = false, dff_mode = false, keepff = false, cleanup = true; bool show_tempdir = false, sop_mode = false; @@ -1556,6 +1687,10 @@ struct AbcPass : public Pass { liberty_files.push_back(args[++argidx]); continue; } + if (arg == "-genlib" && argidx+1 < args.size()) { + genlib_files.push_back(args[++argidx]); + continue; + } if (arg == "-constr" && argidx+1 < args.size()) { constr_file = args[++argidx]; continue; @@ -1645,7 +1780,8 @@ struct AbcPass : public Pass { } extra_args(args, argidx, design); - if (liberty_files.empty() && !default_liberty_file.empty()) liberty_files.push_back(default_liberty_file); + if (genlib_files.empty() && liberty_files.empty() && !default_liberty_file.empty()) + liberty_files.push_back(default_liberty_file); rewrite_filename(script_file); if (!script_file.empty() && !is_absolute_path(script_file) && script_file[0] != '+') @@ -1655,6 +1791,11 @@ struct AbcPass : public Pass { if (!liberty_files[i].empty() && !is_absolute_path(liberty_files[i])) liberty_files[i] = std::string(pwd) + "/" + liberty_files[i]; } + for (int i = 0; i < GetSize(genlib_files); i++) { + rewrite_filename(genlib_files[i]); + if (!genlib_files[i].empty() && !is_absolute_path(genlib_files[i])) + genlib_files[i] = std::string(pwd) + "/" + genlib_files[i]; + } rewrite_filename(constr_file); if (!constr_file.empty() && !is_absolute_path(constr_file)) constr_file = std::string(pwd) + "/" + constr_file; @@ -1818,10 +1959,10 @@ struct AbcPass : public Pass { } } - if (!lut_costs.empty() && !liberty_files.empty()) - log_cmd_error("Got -lut and -liberty! These two options are exclusive.\n"); - if (!constr_file.empty() && liberty_files.empty()) - log_cmd_error("Got -constr but no -liberty!\n"); + if (!lut_costs.empty() && !(liberty_files.empty() && genlib_files.empty())) + log_cmd_error("Got -lut and -liberty/-genlib! These two options are exclusive.\n"); + if (!constr_file.empty() && (liberty_files.empty() && genlib_files.empty())) + log_cmd_error("Got -constr but no -liberty/-genlib!\n"); if (enabled_gates.empty()) { enabled_gates.insert("AND"); @@ -1851,7 +1992,7 @@ struct AbcPass : public Pass { initvals.set(&assign_map, mod); if (!dff_mode || !clk_str.empty()) { - abc_module(design, mod, script_file, exe_file, liberty_files, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff, + abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode, abc_dress); continue; } @@ -1859,18 +2000,18 @@ struct AbcPass : public Pass { CellTypes ct(design); std::vector<RTLIL::Cell*> all_cells = mod->selected_cells(); - std::set<RTLIL::Cell*> unassigned_cells(all_cells.begin(), all_cells.end()); + pool<RTLIL::Cell*> unassigned_cells(all_cells.begin(), all_cells.end()); - std::set<RTLIL::Cell*> expand_queue, next_expand_queue; - std::set<RTLIL::Cell*> expand_queue_up, next_expand_queue_up; - std::set<RTLIL::Cell*> expand_queue_down, next_expand_queue_down; + pool<RTLIL::Cell*> expand_queue, next_expand_queue; + pool<RTLIL::Cell*> expand_queue_up, next_expand_queue_up; + pool<RTLIL::Cell*> expand_queue_down, next_expand_queue_down; - typedef tuple<bool, RTLIL::SigSpec, bool, RTLIL::SigSpec> clkdomain_t; - std::map<clkdomain_t, std::vector<RTLIL::Cell*>> assigned_cells; - std::map<RTLIL::Cell*, clkdomain_t> assigned_cells_reverse; + typedef tuple<bool, RTLIL::SigSpec, bool, RTLIL::SigSpec, bool, RTLIL::SigSpec, bool, RTLIL::SigSpec> clkdomain_t; + dict<clkdomain_t, std::vector<RTLIL::Cell*>> assigned_cells; + dict<RTLIL::Cell*, clkdomain_t> assigned_cells_reverse; - std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_bit, cell_to_bit_up, cell_to_bit_down; - std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> bit_to_cell, bit_to_cell_up, bit_to_cell_down; + dict<RTLIL::Cell*, pool<RTLIL::SigBit>> cell_to_bit, cell_to_bit_up, cell_to_bit_down; + dict<RTLIL::SigBit, pool<RTLIL::Cell*>> bit_to_cell, bit_to_cell_up, bit_to_cell_down; for (auto cell : all_cells) { @@ -1893,19 +2034,30 @@ struct AbcPass : public Pass { } } - if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_))) - { - key = clkdomain_t(cell->type == ID($_DFF_P_), assign_map(cell->getPort(ID::C)), true, RTLIL::SigSpec()); - } - else - if (cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) - { - bool this_clk_pol = cell->type.in(ID($_DFFE_PN_), ID($_DFFE_PP_)); - bool this_en_pol = cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_)); - key = clkdomain_t(this_clk_pol, assign_map(cell->getPort(ID::C)), this_en_pol, assign_map(cell->getPort(ID::E))); - } - else + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(&initvals, cell); + if (!ff.has_clk) + continue; + if (ff.has_gclk) + continue; + if (ff.has_aload) + continue; + if (ff.has_sr) + continue; + if (!ff.is_fine) continue; + key = clkdomain_t( + ff.pol_clk, + ff.sig_clk, + ff.has_ce ? ff.pol_ce : true, + ff.has_ce ? assign_map(ff.sig_ce) : RTLIL::SigSpec(), + ff.has_arst ? ff.pol_arst : true, + ff.has_arst ? assign_map(ff.sig_arst) : RTLIL::SigSpec(), + ff.has_srst ? ff.pol_srst : true, + ff.has_srst ? assign_map(ff.sig_srst) : RTLIL::SigSpec() + ); unassigned_cells.erase(cell); expand_queue.insert(cell); @@ -1979,7 +2131,7 @@ struct AbcPass : public Pass { expand_queue.swap(next_expand_queue); } - clkdomain_t key(true, RTLIL::SigSpec(), true, RTLIL::SigSpec()); + clkdomain_t key(true, RTLIL::SigSpec(), true, RTLIL::SigSpec(), true, RTLIL::SigSpec(), true, RTLIL::SigSpec()); for (auto cell : unassigned_cells) { assigned_cells[key].push_back(cell); assigned_cells_reverse[cell] = key; @@ -1987,16 +2139,22 @@ struct AbcPass : public Pass { log_header(design, "Summary of detected clock domains:\n"); for (auto &it : assigned_cells) - log(" %d cells in clk=%s%s, en=%s%s\n", GetSize(it.second), + log(" %d cells in clk=%s%s, en=%s%s, arst=%s%s, srst=%s%s\n", GetSize(it.second), std::get<0>(it.first) ? "" : "!", log_signal(std::get<1>(it.first)), - std::get<2>(it.first) ? "" : "!", log_signal(std::get<3>(it.first))); + std::get<2>(it.first) ? "" : "!", log_signal(std::get<3>(it.first)), + std::get<4>(it.first) ? "" : "!", log_signal(std::get<5>(it.first)), + std::get<6>(it.first) ? "" : "!", log_signal(std::get<7>(it.first))); for (auto &it : assigned_cells) { clk_polarity = std::get<0>(it.first); clk_sig = assign_map(std::get<1>(it.first)); en_polarity = std::get<2>(it.first); en_sig = assign_map(std::get<3>(it.first)); - abc_module(design, mod, script_file, exe_file, liberty_files, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$", + arst_polarity = std::get<4>(it.first); + arst_sig = assign_map(std::get<5>(it.first)); + srst_polarity = std::get<6>(it.first); + srst_sig = assign_map(std::get<7>(it.first)); + abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$", keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode, abc_dress); assign_map.set(mod); } diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 1f00fc3e7..fe0802d70 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -74,12 +74,18 @@ struct Abc9Pass : public ScriptPass /* Comm3 */ "&synch2 -K 6 -C 500; &if -m "/*"-E 5"*/" {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ /* Comm2 */ "&dch -C 500; &if -m {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save; "\ "&load"; - // Based on ABC's &flow3 + // Based on ABC's &flow3 -m RTLIL::constpad["abc9.script.flow3"] = "+&scorr; &sweep;" \ "&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\ "&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &save; &load;"\ "&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &save; &load;"\ "&mfs"; + // As above, but with &mfs calls as in the original &flow3 + RTLIL::constpad["abc9.script.flow3mfs"] = "+&scorr; &sweep;" \ + "&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\ + "&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\ + "&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\ + "&mfs"; } void help() override { diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc index 7a6959971..acafb0b65 100644 --- a/passes/techmap/abc9_ops.cc +++ b/passes/techmap/abc9_ops.cc @@ -155,6 +155,9 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) r.first->second = new Design; Design *unmap_design = r.first->second; + // Keep track of derived versions of modules that we haven't used, to prevent these being used for unwanted techmaps later on. + pool<IdString> unused_derived; + for (auto module : design->selected_modules()) for (auto cell : module->cells()) { auto inst_module = design->module(cell->type); @@ -167,12 +170,9 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) derived_module = inst_module; } else { - // Check potential for any one of those three - // (since its value may depend on a parameter, but not its existence) - if (!inst_module->has_attribute(ID::abc9_flop) && !inst_module->has_attribute(ID::abc9_box) && !inst_module->get_bool_attribute(ID::abc9_bypass)) - continue; derived_type = inst_module->derive(design, cell->parameters); derived_module = design->module(derived_type); + unused_derived.insert(derived_type); } if (derived_module->get_bool_attribute(ID::abc9_flop)) { @@ -180,13 +180,23 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) continue; } else { - if (!derived_module->get_bool_attribute(ID::abc9_box) && !derived_module->get_bool_attribute(ID::abc9_bypass)) { + bool has_timing = false; + for (auto derived_cell : derived_module->cells()) { + if (derived_cell->type.in(ID($specify2), ID($specify3), ID($specrule))) { + // If the module contains timing; then we potentially care about deriving its content too, + // as timings (or associated port widths) could be dependent on parameters. + has_timing = true; + break; + } + } + if (!derived_module->get_bool_attribute(ID::abc9_box) && !derived_module->get_bool_attribute(ID::abc9_bypass) && !has_timing) { if (unmap_design->module(derived_type)) { // If derived_type is present in unmap_design, it means that it was processed previously, but found to be incompatible -- e.g. if // it contained a non-zero initial state. In this case, continue to replace the cell type/parameters so that it has the same properties // as a compatible type, yet will be safely unmapped later cell->type = derived_type; cell->parameters.clear(); + unused_derived.erase(derived_type); } continue; } @@ -245,7 +255,11 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) cell->type = derived_type; cell->parameters.clear(); + unused_derived.erase(derived_type); } + for (auto unused : unused_derived) { + design->remove(design->module(unused)); + } } void prep_bypass(RTLIL::Design *design) @@ -648,40 +662,38 @@ void prep_delays(RTLIL::Design *design, bool dff_mode) auto inst_module = design->module(cell->type); log_assert(inst_module); - auto &t = timing.at(cell->type).required; - for (auto &conn : cell->connections_) { - auto port_wire = inst_module->wire(conn.first); + for (auto &i : timing.at(cell->type).required) { + auto port_wire = inst_module->wire(i.first.name); if (!port_wire) log_error("Port %s in cell %s (type %s) from module %s does not actually exist", - log_id(conn.first), log_id(cell), log_id(cell->type), log_id(module)); - if (!port_wire->port_input) - continue; - if (conn.second.is_fully_const()) + log_id(i.first.name), log_id(cell), log_id(cell->type), log_id(module)); + log_assert(port_wire->port_input); + + auto d = i.second.first; + if (d == 0) continue; - SigSpec O = module->addWire(NEW_ID, GetSize(conn.second)); - for (int i = 0; i < GetSize(conn.second); i++) { - auto d = t.at(TimingInfo::NameBit(conn.first,i), 0); - if (d == 0) - continue; + auto offset = i.first.offset; + auto O = module->addWire(NEW_ID); + auto rhs = cell->getPort(i.first.name); #ifndef NDEBUG - if (ys_debug(1)) { - static std::set<std::tuple<IdString,IdString,int>> seen; - if (seen.emplace(cell->type, conn.first, i).second) log("%s.%s[%d] abc9_required = %d\n", - log_id(cell->type), log_id(conn.first), i, d); - } + if (ys_debug(1)) { + static pool<std::pair<IdString,TimingInfo::NameBit>> seen; + if (seen.emplace(cell->type, i.first).second) log("%s.%s[%d] abc9_required = %d\n", + log_id(cell->type), log_id(i.first.name), offset, d); + } #endif - auto r = box_cache.insert(d); - if (r.second) { - r.first->second = delay_module->derive(design, {{ID::DELAY, d}}); - log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY=")); - } - auto box = module->addCell(NEW_ID, r.first->second); - box->setPort(ID::I, conn.second[i]); - box->setPort(ID::O, O[i]); - conn.second[i] = O[i]; + auto r = box_cache.insert(d); + if (r.second) { + r.first->second = delay_module->derive(design, {{ID::DELAY, d}}); + log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY=")); } + auto box = module->addCell(NEW_ID, r.first->second); + box->setPort(ID::I, rhs[offset]); + box->setPort(ID::O, O); + rhs[offset] = O; + cell->setPort(i.first.name, rhs); } } } @@ -1006,16 +1018,16 @@ void prep_box(RTLIL::Design *design) log_assert(GetSize(wire) == 1); auto it = t.find(TimingInfo::NameBit(port_name,0)); if (it == t.end()) - // Assume no connectivity if no setup time - ss << "-"; + // Assume that no setup time means zero + ss << 0; else { - ss << it->second; + ss << it->second.first; #ifndef NDEBUG if (ys_debug(1)) { static std::set<std::pair<IdString,IdString>> seen; if (seen.emplace(module->name, port_name).second) log("%s.%s abc9_required = %d\n", log_id(module), - log_id(port_name), it->second); + log_id(port_name), it->second.first); } #endif } diff --git a/passes/techmap/bmuxmap.cc b/passes/techmap/bmuxmap.cc new file mode 100644 index 000000000..03673c278 --- /dev/null +++ b/passes/techmap/bmuxmap.cc @@ -0,0 +1,76 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BmuxmapPass : public Pass { + BmuxmapPass() : Pass("bmuxmap", "transform $bmux cells to trees of $mux cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" bmuxmap [selection]\n"); + log("\n"); + log("This pass transforms $bmux cells to trees of $mux cells.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing BMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($bmux)) + continue; + + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + int width = GetSize(cell->getPort(ID::Y)); + + for (int idx = 0; idx < GetSize(sel); idx++) { + SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); + for (int i = 0; i < GetSize(new_data); i += width) { + RTLIL::Cell *mux = module->addMux(NEW_ID, + data.extract(i*2, width), + data.extract(i*2+width, width), + sel[idx], + new_data.extract(i, width)); + mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } + data = new_data; + } + + module->connect(cell->getPort(ID::Y), data); + module->remove(cell); + } + } +} BmuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/demuxmap.cc b/passes/techmap/demuxmap.cc new file mode 100644 index 000000000..292b18bad --- /dev/null +++ b/passes/techmap/demuxmap.cc @@ -0,0 +1,80 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Marcelina Kościelnicka <mwk@0x04.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct DemuxmapPass : public Pass { + DemuxmapPass() : Pass("demuxmap", "transform $demux cells to $eq + $mux cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" demuxmap [selection]\n"); + log("\n"); + log("This pass transforms $demux cells to a bunch of equality comparisons.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing DEMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($demux)) + continue; + + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + SigSpec out = cell->getPort(ID::Y); + int width = GetSize(cell->getPort(ID::A)); + + for (int i = 0; i < 1 << GetSize(sel); i++) { + if (width == 1 && data == State::S1) { + RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), out[i]); + eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } else { + Wire *eq = module->addWire(NEW_ID); + RTLIL::Cell *eq_cell = module->addEq(NEW_ID, sel, Const(i, GetSize(sel)), eq); + eq_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + RTLIL::Cell *mux = module->addMux(NEW_ID, + Const(State::S0, width), + data, + eq, + out.extract(i*width, width)); + mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } + } + + module->remove(cell); + } + } +} DemuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/dfflegalize.cc b/passes/techmap/dfflegalize.cc index c1e7e557d..1d99caa3a 100644 --- a/passes/techmap/dfflegalize.cc +++ b/passes/techmap/dfflegalize.cc @@ -20,6 +20,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/ffinit.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -27,38 +28,42 @@ PRIVATE_NAMESPACE_BEGIN enum FfType { FF_DFF, FF_DFFE, - FF_ADFF0, - FF_ADFF1, - FF_ADFFE0, - FF_ADFFE1, + FF_ADFF, + FF_ADFFE, + FF_ALDFF, + FF_ALDFFE, FF_DFFSR, FF_DFFSRE, - FF_SDFF0, - FF_SDFF1, - FF_SDFFE0, - FF_SDFFE1, - FF_SDFFCE0, - FF_SDFFCE1, + FF_SDFF, + FF_SDFFE, + FF_SDFFCE, + FF_RLATCH, FF_SR, FF_DLATCH, - FF_ADLATCH0, - FF_ADLATCH1, + FF_ADLATCH, FF_DLATCHSR, NUM_FFTYPES, }; enum FfNeg { - NEG_R = 0x1, - NEG_S = 0x2, - NEG_E = 0x4, - NEG_C = 0x8, - NUM_NEG = 0x10, + NEG_CE = 0x1, + NEG_R = 0x2, + NEG_S = 0x4, + NEG_L = 0x8, + NEG_C = 0x10, + NUM_NEG = 0x20, }; enum FfInit { INIT_X = 0x1, INIT_0 = 0x2, INIT_1 = 0x4, + INIT_X_R0 = 0x10, + INIT_0_R0 = 0x20, + INIT_1_R0 = 0x40, + INIT_X_R1 = 0x100, + INIT_0_R1 = 0x200, + INIT_1_R1 = 0x400, }; struct DffLegalizePass : public Pass { @@ -101,6 +106,8 @@ struct DffLegalizePass : public Pass { log("- $_DFFE_[NP][NP]_\n"); log("- $_DFF_[NP][NP][01]_\n"); log("- $_DFFE_[NP][NP][01][NP]_\n"); + log("- $_ALDFF_[NP][NP]_\n"); + log("- $_ALDFFE_[NP][NP][NP]_\n"); log("- $_DFFSR_[NP][NP][NP]_\n"); log("- $_DFFSRE_[NP][NP][NP][NP]_\n"); log("- $_SDFF_[NP][NP][01]_\n"); @@ -151,18 +158,30 @@ struct DffLegalizePass : public Pass { int supported_cells[NUM_FFTYPES]; // Aggregated for all *dff* cells. int supported_dff; + // Aggregated for all *dffe* cells. + int supported_dffe; // Aggregated for all dffsr* cells. int supported_dffsr; - // Aggregated for all adff* cells. - int supported_adff0; - int supported_adff1; + // Aggregated for all aldff cells. + int supported_aldff; + // Aggregated for all aldffe cells. + int supported_aldffe; + // Aggregated for all adff* cells and trivial emulations. + int supported_adff; + // Aggregated for all adffe* cells and trivial emulations. + int supported_adffe; // Aggregated for all sdff* cells. - int supported_sdff0; - int supported_sdff1; + int supported_sdff; // Aggregated for all ways to obtain a SR latch. int supported_sr; + int supported_sr_plain; // Aggregated for all *dlatch* cells. int supported_dlatch; + int supported_dlatch_plain; + // Aggregated for all ways to obtain an R latch. + int supported_rlatch; + // Aggregated for all adlatch cells and trivial emulations. + int supported_adlatch; int mince; int minsrst; @@ -179,736 +198,794 @@ struct DffLegalizePass : public Pass { res |= INIT_1; if (mask & INIT_1) res |= INIT_0; + if (mask & INIT_X_R0) + res |= INIT_X_R1; + if (mask & INIT_0_R0) + res |= INIT_1_R1; + if (mask & INIT_1_R0) + res |= INIT_0_R1; + if (mask & INIT_X_R1) + res |= INIT_X_R0; + if (mask & INIT_0_R1) + res |= INIT_1_R0; + if (mask & INIT_1_R1) + res |= INIT_0_R0; return res; } - void handle_ff(Cell *cell) { - std::string type_str = cell->type.str(); - - FfType ff_type; - int ff_neg = 0; - SigSpec sig_d; - SigSpec sig_q; - SigSpec sig_c; - SigSpec sig_e; - SigSpec sig_r; - SigSpec sig_s; - bool has_srst = false; - - if (cell->hasPort(ID::D)) - sig_d = cell->getPort(ID::D); - if (cell->hasPort(ID::Q)) - sig_q = cell->getPort(ID::Q); - if (cell->hasPort(ID::C)) - sig_c = cell->getPort(ID::C); - if (cell->hasPort(ID::E)) - sig_e = cell->getPort(ID::E); - if (cell->hasPort(ID::R)) - sig_r = cell->getPort(ID::R); - if (cell->hasPort(ID::S)) - sig_s = cell->getPort(ID::S); - - if (type_str.substr(0, 5) == "$_SR_") { - ff_type = FF_SR; - if (type_str[5] == 'N') - ff_neg |= NEG_S; - if (type_str[6] == 'N') - ff_neg |= NEG_R; - } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 8) { - ff_type = FF_DFF; - if (type_str[6] == 'N') - ff_neg |= NEG_C; - } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 10) { - ff_type = FF_DFFE; - if (type_str[7] == 'N') - ff_neg |= NEG_C; - if (type_str[8] == 'N') - ff_neg |= NEG_E; - } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 10) { - ff_type = type_str[8] == '1' ? FF_ADFF1 : FF_ADFF0; - if (type_str[6] == 'N') - ff_neg |= NEG_C; - if (type_str[7] == 'N') - ff_neg |= NEG_R; - } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 12) { - ff_type = type_str[9] == '1' ? FF_ADFFE1 : FF_ADFFE0; - if (type_str[7] == 'N') - ff_neg |= NEG_C; - if (type_str[8] == 'N') - ff_neg |= NEG_R; - if (type_str[10] == 'N') - ff_neg |= NEG_E; - } else if (type_str.substr(0, 8) == "$_DFFSR_" && type_str.size() == 12) { - ff_type = FF_DFFSR; - if (type_str[8] == 'N') - ff_neg |= NEG_C; - if (type_str[9] == 'N') - ff_neg |= NEG_S; - if (type_str[10] == 'N') - ff_neg |= NEG_R; - } else if (type_str.substr(0, 9) == "$_DFFSRE_" && type_str.size() == 14) { - ff_type = FF_DFFSRE; - if (type_str[9] == 'N') - ff_neg |= NEG_C; - if (type_str[10] == 'N') - ff_neg |= NEG_S; - if (type_str[11] == 'N') - ff_neg |= NEG_R; - if (type_str[12] == 'N') - ff_neg |= NEG_E; - } else if (type_str.substr(0, 7) == "$_SDFF_" && type_str.size() == 11) { - ff_type = type_str[9] == '1' ? FF_SDFF1 : FF_SDFF0; - if (type_str[7] == 'N') - ff_neg |= NEG_C; - if (type_str[8] == 'N') - ff_neg |= NEG_R; - has_srst = true; - } else if (type_str.substr(0, 8) == "$_SDFFE_" && type_str.size() == 13) { - ff_type = type_str[10] == '1' ? FF_SDFFE1 : FF_SDFFE0; - if (type_str[8] == 'N') - ff_neg |= NEG_C; - if (type_str[9] == 'N') - ff_neg |= NEG_R; - if (type_str[11] == 'N') - ff_neg |= NEG_E; - has_srst = true; - } else if (type_str.substr(0, 9) == "$_SDFFCE_" && type_str.size() == 14) { - ff_type = type_str[11] == '1' ? FF_SDFFCE1 : FF_SDFFCE0; - if (type_str[9] == 'N') - ff_neg |= NEG_C; - if (type_str[10] == 'N') - ff_neg |= NEG_R; - if (type_str[12] == 'N') - ff_neg |= NEG_E; - has_srst = true; - } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 11) { - ff_type = FF_DLATCH; - if (type_str[9] == 'N') - ff_neg |= NEG_E; - } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 13) { - ff_type = type_str[11] == '1' ? FF_ADLATCH1 : FF_ADLATCH0; - if (type_str[9] == 'N') - ff_neg |= NEG_E; - if (type_str[10] == 'N') - ff_neg |= NEG_R; - } else if (type_str.substr(0, 11) == "$_DLATCHSR_" && type_str.size() == 15) { - ff_type = FF_DLATCHSR; - if (type_str[11] == 'N') - ff_neg |= NEG_E; - if (type_str[12] == 'N') - ff_neg |= NEG_S; - if (type_str[13] == 'N') - ff_neg |= NEG_R; + int get_ff_type(const FfData &ff) { + if (ff.has_clk) { + if (ff.has_sr) { + return ff.has_ce ? FF_DFFSRE : FF_DFFSR; + } else if (ff.has_arst) { + return ff.has_ce ? FF_ADFFE : FF_ADFF; + } else if (ff.has_aload) { + return ff.has_ce ? FF_ALDFFE : FF_ALDFF; + } else if (ff.has_srst) { + if (ff.has_ce) + return ff.ce_over_srst ? FF_SDFFCE : FF_SDFFE; + else + return FF_SDFF; + } else { + return ff.has_ce ? FF_DFFE : FF_DFF; + } } else { - log_warning("Ignoring unknown ff type %s [%s.%s].\n", log_id(cell->type), log_id(cell->module->name), log_id(cell->name)); - return; + if (ff.has_aload) { + if (ff.has_sr) + return FF_DLATCHSR; + else if (ff.has_arst) + return FF_ADLATCH; + else + return FF_DLATCH; + } else { + if (ff.has_sr) { + return FF_SR; + } else if (ff.has_arst) { + return FF_RLATCH; + } else { + log_assert(0); + return 0; + } + } + } + } + + int get_initmask(FfData &ff) { + int res = 0; + if (ff.val_init[0] == State::S0) + res = INIT_0; + else if (ff.val_init[0] == State::S1) + res = INIT_1; + else + res = INIT_X; + if (ff.has_arst) { + if (ff.val_arst[0] == State::S0) + res <<= 4; + else if (ff.val_arst[0] == State::S1) + res <<= 8; + } else if (ff.has_srst) { + if (ff.val_srst[0] == State::S0) + res <<= 4; + else if (ff.val_srst[0] == State::S1) + res <<= 8; + } + return res; + } + + void fail_ff(const FfData &ff, const char *reason) { + log_error("FF %s.%s (type %s) cannot be legalized: %s\n", log_id(ff.module->name), log_id(ff.cell->name), log_id(ff.cell->type), reason); + } + + bool try_flip(FfData &ff, int supported_mask) { + int initmask = get_initmask(ff); + if (supported_mask & initmask) + return true; + if (supported_mask & flip_initmask(initmask)) { + ff.flip_bits({0}); + return true; } + return false; + } - State initval = initvals(sig_q[0]); - - FfInit initmask = INIT_X; - if (initval == State::S0) - initmask = INIT_0; - else if (initval == State::S1) - initmask = INIT_1; - const char *reason; + void emulate_split_init_arst(FfData &ff) { + ff.remove(); - bool kill_ce = mince && GetSize(sig_c) && GetSize(sig_e) && sig_e[0].wire && ce_used[sig_e[0]] < mince; - bool kill_srst = minsrst && has_srst && sig_r[0].wire && srst_used[sig_r[0]] < minsrst; + FfData ff_dff(ff.module, &initvals, NEW_ID); + ff_dff.width = ff.width; + ff_dff.has_aload = ff.has_aload; + ff_dff.sig_aload = ff.sig_aload; + ff_dff.pol_aload = ff.pol_aload; + ff_dff.sig_ad = ff.sig_ad; + ff_dff.has_clk = ff.has_clk; + ff_dff.sig_clk = ff.sig_clk; + ff_dff.pol_clk = ff.pol_clk; + ff_dff.sig_d = ff.sig_d; + ff_dff.has_ce = ff.has_ce; + ff_dff.sig_ce = ff.sig_ce; + ff_dff.pol_ce = ff.pol_ce; + ff_dff.sig_q = ff.module->addWire(NEW_ID, ff.width); + ff_dff.val_init = ff.val_init; + ff_dff.is_fine = ff.is_fine; - while (!(supported_cells[ff_type] & initmask) || kill_ce || kill_srst) { - // Well, cell is not directly supported. Decide how to deal with it. + FfData ff_adff(ff.module, &initvals, NEW_ID); + ff_adff.width = ff.width; + ff_adff.has_aload = ff.has_aload; + ff_adff.sig_aload = ff.sig_aload; + ff_adff.pol_aload = ff.pol_aload; + ff_adff.sig_ad = ff.sig_ad; + ff_adff.has_clk = ff.has_clk; + ff_adff.sig_clk = ff.sig_clk; + ff_adff.pol_clk = ff.pol_clk; + ff_adff.sig_d = ff.sig_d; + ff_adff.has_ce = ff.has_ce; + ff_adff.sig_ce = ff.sig_ce; + ff_adff.pol_ce = ff.pol_ce; + ff_adff.sig_q = ff.module->addWire(NEW_ID, ff.width); + ff_adff.val_init = Const(State::Sx, ff.width); + ff_adff.has_arst = true; + ff_adff.sig_arst = ff.sig_arst; + ff_adff.pol_arst = ff.pol_arst; + ff_adff.val_arst = ff.val_arst; + ff_adff.is_fine = ff.is_fine; - if (ff_type == FF_DFF || ff_type == FF_DFFE) { - if (kill_ce) { - ff_type = FF_DFF; - goto unmap_enable; - } - if (!(supported_dff & initmask)) { - // This init value is not supported at all... - if (supported_dff & flip_initmask(initmask)) { - // The other one is, though. Negate D, Q, and init. -flip_dqi: - if (initval == State::S0) { - initval = State::S1; - initmask = INIT_1; - } else if (initval == State::S1) { - initval = State::S0; - initmask = INIT_0; - } - if (ff_type != FF_SR) - sig_d = cell->module->NotGate(NEW_ID, sig_d[0]); - SigBit new_q = SigSpec(cell->module->addWire(NEW_ID))[0]; - cell->module->addNotGate(NEW_ID, new_q, sig_q[0]); - initvals.remove_init(sig_q[0]); - initvals.set_init(new_q, initval); - sig_q = new_q; - continue; - } - if (!supported_dff) - reason = "dffs are not supported"; - else - reason = "initialized dffs are not supported"; - goto error; - } + FfData ff_sel(ff.module, &initvals, NEW_ID); + ff_sel.width = 1; + ff_sel.sig_q = ff.module->addWire(NEW_ID); + ff_sel.has_arst = true; + ff_sel.sig_arst = ff.sig_arst; + ff_sel.pol_arst = ff.pol_arst; + ff_sel.val_arst = State::S1; + ff_sel.val_init = State::S0; + ff_sel.is_fine = ff.is_fine; - // Some DFF is supported with this init val. Just pick a type. - if (ff_type == FF_DFF) { - // Try adding a set or reset pin. - for (auto new_type: {FF_SDFF0, FF_SDFF1, FF_ADFF0, FF_ADFF1}) - if (supported_cells[new_type] & initmask) { - ff_type = new_type; - sig_r = State::S0; - goto cell_ok; - } - // Try adding both. - if (supported_cells[FF_DFFSR] & initmask) { - ff_type = FF_DFFSR; - sig_r = State::S0; - sig_s = State::S0; - break; - } - // Nope. Will need to add enable and go the DFFE route. - sig_e = State::S1; - if (supported_cells[FF_DFFE] & initmask) { - ff_type = FF_DFFE; - break; - } - } - // Try adding a set or reset pin. - for (auto new_type: {FF_SDFFE0, FF_SDFFE1, FF_SDFFCE0, FF_SDFFCE1, FF_ADFFE0, FF_ADFFE1}) - if (supported_cells[new_type] & initmask) { - ff_type = new_type; - sig_r = State::S0; - goto cell_ok; - } - // Try adding both. - if (supported_cells[FF_DFFSRE] & initmask) { - ff_type = FF_DFFSRE; - sig_r = State::S0; - sig_s = State::S0; - break; - } + if (ff.is_fine) + ff.module->addMuxGate(NEW_ID, ff_dff.sig_q, ff_adff.sig_q, ff_sel.sig_q, ff.sig_q); + else + ff.module->addMux(NEW_ID, ff_dff.sig_q, ff_adff.sig_q, ff_sel.sig_q, ff.sig_q); + + legalize_ff(ff_dff); + legalize_ff(ff_adff); + legalize_ff(ff_sel); + } - // Seems that no DFFs with enable are supported. - // The enable input needs to be unmapped. - // This should not be reached if we started from plain DFF. - log_assert(ff_type == FF_DFFE); - ff_type = FF_DFF; -unmap_enable: - if (ff_neg & NEG_E) - sig_d = cell->module->MuxGate(NEW_ID, sig_d[0], sig_q[0], sig_e[0]); + void emulate_split_set_clr(FfData &ff) { + // No native DFFSR. However, if we can conjure + // a SR latch and ADFF, it can still be emulated. + int initmask = get_initmask(ff); + int flipmask = flip_initmask(initmask); + bool init_clr = true; + bool init_set = true; + State initsel = State::Sx; + int supported_arst = ff.has_clk ? supported_adff : supported_adlatch; + bool init_clr_ok = (supported_arst & initmask << 4) || (supported_arst & flipmask << 8); + bool init_set_ok = (supported_arst & initmask << 8) || (supported_arst & flipmask << 4); + if (init_clr_ok && init_set_ok && supported_sr) { + // OK + } else if (init_clr_ok && (supported_sr & INIT_0)) { + init_set = false; + initsel = State::S0; + } else if (init_set_ok && (supported_sr & INIT_1)) { + init_clr = false; + initsel = State::S1; + } else if (init_clr_ok && (supported_sr & INIT_1)) { + init_set = false; + initsel = State::S0; + } else if (init_set_ok && (supported_sr & INIT_0)) { + init_clr = false; + initsel = State::S1; + } else { + if (ff.has_clk) { + if (!supported_dffsr) + fail_ff(ff, "dffs with async set and reset are not supported"); else - sig_d = cell->module->MuxGate(NEW_ID, sig_q[0], sig_d[0], sig_e[0]); - ff_neg &= ~NEG_E; - sig_e = SigSpec(); - kill_ce = false; - // Now try again as plain DFF. - continue; - } else if (ff_type == FF_ADFF0 || ff_type == FF_ADFF1 || ff_type == FF_ADFFE0 || ff_type == FF_ADFFE1) { - bool has_set = ff_type == FF_ADFF1 || ff_type == FF_ADFFE1; - bool has_en = ff_type == FF_ADFFE0 || ff_type == FF_ADFFE1; - if (kill_ce) { - ff_type = has_set ? FF_ADFF1 : FF_ADFF0; - goto unmap_enable; - } - if (!has_en && (supported_cells[has_set ? FF_ADFFE1 : FF_ADFFE0] & initmask)) { - // Just add enable. - sig_e = State::S1; - ff_type = has_set ? FF_ADFFE1 : FF_ADFFE0; - break; - } - if (supported_cells[has_en ? FF_DFFSRE : FF_DFFSR] & initmask) { -adff_to_dffsr: - // Throw in a set/reset, retry in DFFSR/DFFSRE branch. - if (has_set) { - sig_s = sig_r; - sig_r = State::S0; - if (ff_neg & NEG_R) { - ff_neg &= ~NEG_R; - ff_neg |= NEG_S; - } - } else { - sig_s = State::S0; - } - if (has_en) - ff_type = FF_DFFSRE; - else - ff_type = FF_DFFSR; - continue; - } - if (has_en && (supported_cells[has_set ? FF_ADFF1 : FF_ADFF0] & initmask)) { - // Unmap enable. - ff_type = has_set ? FF_ADFF1 : FF_ADFF0; - goto unmap_enable; - } - if (supported_dffsr & initmask) { - goto adff_to_dffsr; - } - log_assert(!((has_set ? supported_adff1 : supported_adff0) & initmask)); - // Alright, so this particular combination of initval and - // resetval is not natively supported. First, try flipping - // them both to see whether this helps. - int flipmask = flip_initmask(initmask); - if ((has_set ? supported_adff0 : supported_adff1) & flipmask) { - // Checks out, do it. - ff_type = has_en ? (has_set ? FF_ADFFE0 : FF_ADFFE1) : (has_set ? FF_ADFF0 : FF_ADFF1); - goto flip_dqi; - } + fail_ff(ff, "initialized dffs with async set and reset are not supported"); + } else { + if (!supported_cells[FF_DLATCHSR]) + fail_ff(ff, "dlatch with async set and reset are not supported"); + else + fail_ff(ff, "initialized dlatch with async set and reset are not supported"); + } + } - if (!supported_adff0 && !supported_adff1) { - reason = "dffs with async set or reset are not supported"; - goto error; - } - if (!(supported_dff & ~INIT_X)) { - reason = "initialized dffs are not supported"; - goto error; - } - // If we got here, initialized dff is supported, but not this - // particular reset+init combination (nor its negation). - // The only hope left is breaking down to adff + dff + dlatch + mux. - if (!(supported_dlatch & ~INIT_X)) { - reason = "unsupported initial value and async reset value combination"; - goto error; - } + // If we have to unmap enable anyway, do it before breakdown. + if (ff.has_ce && !supported_cells[FF_ADFFE]) + ff.unmap_ce(); - // If we have to unmap enable anyway, do it before breakdown. - if (has_en && !supported_cells[FF_ADFFE0] && !supported_cells[FF_ADFFE1]) { - ff_type = has_set ? FF_ADFF1 : FF_ADFF0; - goto unmap_enable; - } + log_warning("Emulating async set + reset with several FFs and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name)); - log_warning("Emulating mismatched async reset and init with several FFs and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name)); - initvals.remove_init(sig_q[0]); - Wire *adff_q = cell->module->addWire(NEW_ID); - Wire *dff_q = cell->module->addWire(NEW_ID); - Wire *sel_q = cell->module->addWire(NEW_ID); - initvals.set_init(SigBit(dff_q, 0), initval); - initvals.set_init(SigBit(sel_q, 0), State::S0); - Cell *cell_dff; - Cell *cell_adff; - Cell *cell_sel; - if (!has_en) { - cell_dff = cell->module->addDffGate(NEW_ID, sig_c, sig_d, dff_q, !(ff_neg & NEG_C)); - cell_adff = cell->module->addAdffGate(NEW_ID, sig_c, sig_r, sig_d, adff_q, has_set, !(ff_neg & NEG_C), !(ff_neg & NEG_R)); - } else { - cell_dff = cell->module->addDffeGate(NEW_ID, sig_c, sig_e, sig_d, dff_q, !(ff_neg & NEG_C), !(ff_neg & NEG_E)); - cell_adff = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_r, sig_d, adff_q, has_set, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_R)); - } - cell_sel = cell->module->addDlatchGate(NEW_ID, sig_r, State::S1, sel_q, !(ff_neg & NEG_R)); - cell->module->addMuxGate(NEW_ID, dff_q, adff_q, sel_q, sig_q); - - // Bye, cell. - cell->module->remove(cell); - handle_ff(cell_dff); - handle_ff(cell_adff); - handle_ff(cell_sel); - return; - } else if (ff_type == FF_DFFSR || ff_type == FF_DFFSRE) { - if (kill_ce) { - ff_type = FF_DFFSR; - goto unmap_enable; - } - // First, see if mapping/unmapping enable will help. - if (supported_cells[FF_DFFSRE] & initmask) { - ff_type = FF_DFFSRE; - sig_e = State::S1; - break; - } - if (supported_cells[FF_DFFSR] & initmask) { - ff_type = FF_DFFSR; - goto unmap_enable; - } - if (supported_dffsr & flip_initmask(initmask)) { -flip_dqisr:; - log_warning("Flipping D/Q/init and inserting set/reset fixup to handle init value on %s.%s [%s]\n", log_id(cell->module->name), log_id(cell->name), log_id(cell->type)); - SigSpec new_r; - bool neg_r = (ff_neg & NEG_R); - bool neg_s = (ff_neg & NEG_S); - if (!(ff_neg & NEG_S)) { - if (!(ff_neg & NEG_R)) - new_r = cell->module->AndnotGate(NEW_ID, sig_s, sig_r); - else - new_r = cell->module->AndGate(NEW_ID, sig_s, sig_r); - } else { - if (!(ff_neg & NEG_R)) - new_r = cell->module->OrGate(NEW_ID, sig_s, sig_r); - else - new_r = cell->module->OrnotGate(NEW_ID, sig_s, sig_r); - } - ff_neg &= ~(NEG_R | NEG_S); - if (neg_r) - ff_neg |= NEG_S; - if (neg_s) - ff_neg |= NEG_R; - sig_s = sig_r; - sig_r = new_r; - goto flip_dqi; - } - // No native DFFSR. However, if we can conjure - // a SR latch and ADFF, it can still be emulated. - int flipmask = flip_initmask(initmask); - bool init0 = true; - bool init1 = true; - State initsel = State::Sx; - if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && ((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && supported_sr) { - // OK - } else if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && (supported_sr & INIT_0)) { - init1 = false; - initsel = State::S0; - } else if (((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && (supported_sr & INIT_1)) { - init0 = false; - initsel = State::S1; - } else if (((supported_adff0 & initmask) || (supported_adff1 & flipmask)) && (supported_sr & INIT_1)) { - init1 = false; - initsel = State::S0; - } else if (((supported_adff1 & initmask) || (supported_adff0 & flipmask)) && (supported_sr & INIT_0)) { - init0 = false; - initsel = State::S1; - } else { - if (!supported_dffsr) - reason = "dffs with async set and reset are not supported"; - else - reason = "initialized dffs with async set and reset are not supported"; - goto error; - } + log_assert(ff.width == 1); + ff.remove(); - // If we have to unmap enable anyway, do it before breakdown. - if (ff_type == FF_DFFSRE && !supported_cells[FF_ADFFE0] && !supported_cells[FF_ADFFE1]) { - ff_type = FF_DFFSR; - goto unmap_enable; - } + FfData ff_clr(ff.module, &initvals, NEW_ID); + ff_clr.width = ff.width; + ff_clr.has_aload = ff.has_aload; + ff_clr.sig_aload = ff.sig_aload; + ff_clr.pol_aload = ff.pol_aload; + ff_clr.sig_ad = ff.sig_ad; + ff_clr.has_clk = ff.has_clk; + ff_clr.sig_clk = ff.sig_clk; + ff_clr.pol_clk = ff.pol_clk; + ff_clr.sig_d = ff.sig_d; + ff_clr.has_ce = ff.has_ce; + ff_clr.sig_ce = ff.sig_ce; + ff_clr.pol_ce = ff.pol_ce; + ff_clr.has_arst = true; + ff_clr.sig_arst = ff.sig_clr; + ff_clr.pol_arst = ff.pol_clr; + ff_clr.val_arst = Const(State::S0, ff.width); + ff_clr.sig_q = ff.module->addWire(NEW_ID, ff.width); + ff_clr.val_init = init_clr ? ff.val_init : Const(State::Sx, ff.width); + ff_clr.is_fine = ff.is_fine; - log_warning("Emulating async set + reset with several FFs and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name)); - initvals.remove_init(sig_q[0]); - Wire *adff0_q = cell->module->addWire(NEW_ID); - Wire *adff1_q = cell->module->addWire(NEW_ID); - Wire *sel_q = cell->module->addWire(NEW_ID); - if (init0) - initvals.set_init(SigBit(adff0_q, 0), initval); - if (init1) - initvals.set_init(SigBit(adff1_q, 0), initval); - initvals.set_init(SigBit(sel_q, 0), initsel); - Cell *cell_adff0; - Cell *cell_adff1; - Cell *cell_sel; - if (ff_type == FF_DFFSR) { - cell_adff0 = cell->module->addAdffGate(NEW_ID, sig_c, sig_r, sig_d, adff0_q, false, !(ff_neg & NEG_C), !(ff_neg & NEG_R)); - cell_adff1 = cell->module->addAdffGate(NEW_ID, sig_c, sig_s, sig_d, adff1_q, true, !(ff_neg & NEG_C), !(ff_neg & NEG_S)); - } else { - cell_adff0 = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_r, sig_d, adff0_q, false, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_R)); - cell_adff1 = cell->module->addAdffeGate(NEW_ID, sig_c, sig_e, sig_s, sig_d, adff1_q, true, !(ff_neg & NEG_C), !(ff_neg & NEG_E), !(ff_neg & NEG_S)); - } - cell_sel = cell->module->addSrGate(NEW_ID, sig_s, sig_r, sel_q, !(ff_neg & NEG_S), !(ff_neg & NEG_R)); - cell->module->addMuxGate(NEW_ID, adff0_q, adff1_q, sel_q, sig_q); - - // Bye, cell. - cell->module->remove(cell); - handle_ff(cell_adff0); - handle_ff(cell_adff1); - handle_ff(cell_sel); + FfData ff_set(ff.module, &initvals, NEW_ID); + ff_set.width = ff.width; + ff_set.has_aload = ff.has_aload; + ff_set.sig_aload = ff.sig_aload; + ff_set.pol_aload = ff.pol_aload; + ff_set.sig_ad = ff.sig_ad; + ff_set.has_clk = ff.has_clk; + ff_set.sig_clk = ff.sig_clk; + ff_set.pol_clk = ff.pol_clk; + ff_set.sig_d = ff.sig_d; + ff_set.has_ce = ff.has_ce; + ff_set.sig_ce = ff.sig_ce; + ff_set.pol_ce = ff.pol_ce; + ff_set.has_arst = true; + ff_set.sig_arst = ff.sig_set; + ff_set.pol_arst = ff.pol_set; + ff_set.val_arst = Const(State::S1, ff.width); + ff_set.sig_q = ff.module->addWire(NEW_ID, ff.width); + ff_set.val_init = init_set ? ff.val_init : Const(State::Sx, ff.width); + ff_set.is_fine = ff.is_fine; + + FfData ff_sel(ff.module, &initvals, NEW_ID); + ff_sel.width = ff.width; + ff_sel.has_sr = true; + ff_sel.pol_clr = ff.pol_clr; + ff_sel.pol_set = ff.pol_set; + ff_sel.sig_clr = ff.sig_clr; + ff_sel.sig_set = ff.sig_set; + ff_sel.sig_q = ff.module->addWire(NEW_ID, ff.width); + ff_sel.val_init = Const(initsel, ff.width); + ff_sel.is_fine = ff.is_fine; + + if (!ff.is_fine) + ff.module->addMux(NEW_ID, ff_clr.sig_q, ff_set.sig_q, ff_sel.sig_q, ff.sig_q); + else + ff.module->addMuxGate(NEW_ID, ff_clr.sig_q, ff_set.sig_q, ff_sel.sig_q, ff.sig_q); + + legalize_ff(ff_clr); + legalize_ff(ff_set); + legalize_ff(ff_sel); + } + + void legalize_dff(FfData &ff) { + if (!try_flip(ff, supported_dff)) { + if (!supported_dff) + fail_ff(ff, "D flip-flops are not supported"); + else + fail_ff(ff, "initialized D flip-flops are not supported"); + } + + int initmask = get_initmask(ff); + // Some DFF is supported with this init val. Just pick a type. + if (ff.has_ce && !(supported_dffe & initmask)) { + ff.unmap_ce(); + } + + if (!ff.has_ce) { + if (supported_cells[FF_DFF] & initmask) { + legalize_finish(ff); return; - } else if (ff_type == FF_SR) { - if (supported_cells[FF_ADLATCH0] & initmask || supported_cells[FF_ADLATCH1] & flip_initmask(initmask)) { - // Convert to ADLATCH0. May get converted to ADLATCH1. - ff_type = FF_ADLATCH0; - sig_e = sig_s; - sig_d = State::S1; - if (ff_neg & NEG_S) { - ff_neg &= ~NEG_S; - ff_neg |= NEG_E; - } - continue; - } else if (supported_cells[FF_DLATCHSR] & initmask) { - // Upgrade to DLATCHSR. - ff_type = FF_DLATCHSR; - sig_e = State::S0; - sig_d = State::Sx; - break; - } else if (supported_dffsr & initmask) { - // Upgrade to DFFSR. May get further upgraded to DFFSRE. - ff_type = FF_DFFSR; - sig_c = State::S0; - sig_d = State::Sx; - continue; - } else if (supported_sr & flip_initmask(initmask)) { - goto flip_dqisr; - } else { - if (!supported_sr) - reason = "sr latches are not supported"; - else - reason = "initialized sr latches are not supported"; - goto error; - } - } else if (ff_type == FF_DLATCH) { - if (!(supported_dlatch & initmask)) { - // This init value is not supported at all... - if (supported_dlatch & flip_initmask(initmask)) - goto flip_dqi; - - if ((sig_d == State::S0 && (supported_adff0 & initmask)) || - (sig_d == State::S1 && (supported_adff1 & initmask)) || - (sig_d == State::S0 && (supported_adff1 & flip_initmask(initmask))) || - (sig_d == State::S1 && (supported_adff0 & flip_initmask(initmask))) - ) { - // Special case: const-D dlatch can be converted into adff with const clock. - ff_type = (sig_d == State::S0) ? FF_ADFF0 : FF_ADFF1; - if (ff_neg & NEG_E) { - ff_neg &= ~NEG_E; - ff_neg |= NEG_R; - } - sig_r = sig_e; - sig_d = State::Sx; - sig_c = State::S1; - continue; - } + } + // Try adding a set or reset pin. + if (supported_cells[FF_SDFF] & initmask) { + ff.add_dummy_srst(); + legalize_finish(ff); + return; + } + if (supported_cells[FF_ADFF] & initmask) { + ff.add_dummy_arst(); + legalize_finish(ff); + return; + } + // Try adding async load. + if (supported_cells[FF_ALDFF] & initmask) { + ff.add_dummy_aload(); + legalize_finish(ff); + return; + } + // Try adding both. + if (supported_cells[FF_DFFSR] & initmask) { + ff.add_dummy_sr(); + legalize_finish(ff); + return; + } + // Nope. Will need to add enable and go the DFFE route. + ff.add_dummy_ce(); + } + if (supported_cells[FF_DFFE] & initmask) { + legalize_finish(ff); + return; + } + // Try adding a set or reset pin. + if (supported_cells[FF_SDFFCE] & initmask) { + ff.add_dummy_srst(); + ff.ce_over_srst = true; + legalize_finish(ff); + return; + } + if (supported_cells[FF_SDFFE] & initmask) { + ff.add_dummy_srst(); + legalize_finish(ff); + return; + } + if (supported_cells[FF_ADFFE] & initmask) { + ff.add_dummy_arst(); + legalize_finish(ff); + return; + } + if (supported_cells[FF_ALDFFE] & initmask) { + ff.add_dummy_aload(); + legalize_finish(ff); + return; + } + // Try adding both. + if (supported_cells[FF_DFFSRE] & initmask) { + ff.add_dummy_sr(); + legalize_finish(ff); + return; + } + log_assert(0); + } - if (!supported_dlatch) - reason = "dlatch are not supported"; - else - reason = "initialized dlatch are not supported"; - goto error; - } + void legalize_sdffce(FfData &ff) { + if (!try_flip(ff, supported_cells[FF_SDFFCE] | supported_cells[FF_SDFFE])) { + ff.unmap_srst(); + legalize_dff(ff); + return; + } - // Some DLATCH is supported with this init val. Just pick a type. - if (supported_cells[FF_ADLATCH0] & initmask) { - ff_type = FF_ADLATCH0; - sig_r = State::S0; - break; - } - if (supported_cells[FF_ADLATCH1] & initmask) { - ff_type = FF_ADLATCH1; - sig_r = State::S0; - break; - } - if (supported_cells[FF_DLATCHSR] & initmask) { - ff_type = FF_DLATCHSR; - sig_r = State::S0; - sig_s = State::S0; - break; - } + int initmask = get_initmask(ff); + if (supported_cells[FF_SDFFCE] & initmask) { + // OK + } else if (supported_cells[FF_SDFFE] & initmask) { + ff.convert_ce_over_srst(false); + } else { + log_assert(0); + } + legalize_finish(ff); + } + + void legalize_sdff(FfData &ff) { + if (!try_flip(ff, supported_sdff)) { + ff.unmap_srst(); + legalize_dff(ff); + return; + } + + int initmask = get_initmask(ff); + if (!ff.has_ce) { + if (supported_cells[FF_SDFF] & initmask) { + // OK + } else if (supported_cells[FF_SDFFE] & initmask) { + ff.add_dummy_ce(); + } else if (supported_cells[FF_SDFFCE] & initmask) { + ff.add_dummy_ce(); + ff.ce_over_srst = true; + } else { log_assert(0); - } else if (ff_type == FF_ADLATCH0 || ff_type == FF_ADLATCH1) { - if (supported_cells[FF_DLATCHSR] & initmask) { - if (ff_type == FF_ADLATCH1) { - sig_s = sig_r; - sig_r = State::S0; - if (ff_neg & NEG_R) { - ff_neg &= ~NEG_R; - ff_neg |= NEG_S; - } - } else { - sig_s = State::S0; - } - ff_type = FF_DLATCHSR; - break; - } - FfType flip_type = ff_type == FF_ADLATCH0 ? FF_ADLATCH1 : FF_ADLATCH0; - if ((supported_cells[flip_type] | supported_cells[FF_DLATCHSR]) & flip_initmask(initmask)) { - ff_type = flip_type; - goto flip_dqi; - } + } + } else { + log_assert(!ff.ce_over_srst); + if (supported_cells[FF_SDFFE] & initmask) { + // OK + } else if (supported_cells[FF_SDFFCE] & initmask) { + ff.convert_ce_over_srst(true); + } else if (supported_cells[FF_SDFF] & initmask) { + ff.unmap_ce(); + } else { + log_assert(0); + } + } + legalize_finish(ff); + } - if (!supported_cells[FF_ADLATCH0] && !supported_cells[FF_ADLATCH1] && !supported_cells[FF_DLATCHSR]) { - reason = "dlatch with async set or reset are not supported"; - goto error; - } - if (!(supported_dlatch & ~INIT_X)) { - reason = "initialized dlatch are not supported"; - goto error; - } + void legalize_adff(FfData &ff) { + if (!try_flip(ff, supported_adff)) { + if (!supported_adff) + fail_ff(ff, "dffs with async set or reset are not supported"); + if (!(supported_dff & (INIT_0 | INIT_1))) + fail_ff(ff, "initialized dffs are not supported"); - if (!(supported_dlatch & ~INIT_X)) { - reason = "initialized dlatch are not supported"; - goto error; - } - // If we got here, initialized dlatch is supported, but not this - // particular reset+init combination (nor its negation). - // The only hope left is breaking down to adff + dff + dlatch + mux. - - log_warning("Emulating mismatched async reset and init with several latches and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name)); - initvals.remove_init(sig_q[0]); - Wire *adlatch_q = cell->module->addWire(NEW_ID); - Wire *dlatch_q = cell->module->addWire(NEW_ID); - Wire *sel_q = cell->module->addWire(NEW_ID); - initvals.set_init(SigBit(dlatch_q, 0), initval); - initvals.set_init(SigBit(sel_q, 0), State::S0); - Cell *cell_dlatch; - Cell *cell_adlatch; - Cell *cell_sel; - cell_dlatch = cell->module->addDlatchGate(NEW_ID, sig_e, sig_d, dlatch_q, !(ff_neg & NEG_E)); - cell_adlatch = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_r, sig_d, adlatch_q, ff_type == FF_ADLATCH1, !(ff_neg & NEG_E), !(ff_neg & NEG_R)); - cell_sel = cell->module->addDlatchGate(NEW_ID, sig_r, State::S1, sel_q, !(ff_neg & NEG_R)); - cell->module->addMuxGate(NEW_ID, dlatch_q, adlatch_q, sel_q, sig_q); - - // Bye, cell. - cell->module->remove(cell); - handle_ff(cell_dlatch); - handle_ff(cell_adlatch); - handle_ff(cell_sel); + // If we got here, initialized dff is supported, but not this + // particular reset+init combination (nor its negation). + // The only hope left is breaking down to adff + dff + dlatch + mux. + + if (!((supported_rlatch) & (INIT_0_R1 | INIT_1_R0))) + fail_ff(ff, "unsupported initial value and async reset value combination"); + + // If we have to unmap enable anyway, do it before breakdown. + if (ff.has_ce && !supported_cells[FF_ADFFE]) + ff.unmap_ce(); + + if (ff.cell) + log_warning("Emulating mismatched async reset and init with several FFs and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name)); + emulate_split_init_arst(ff); + return; + } + + int initmask = get_initmask(ff); + if (ff.has_ce && !(supported_adffe & initmask)) { + ff.unmap_ce(); + } + + if (!ff.has_ce) { + if (supported_cells[FF_ADFF] & initmask) { + legalize_finish(ff); return; - } else if (ff_type == FF_DLATCHSR) { - if (supported_cells[FF_DLATCHSR] & flip_initmask(initmask)) { - goto flip_dqisr; - } - // No native DFFSR. However, if we can conjure - // a SR latch and ADFF, it can still be emulated. - int flipmask = flip_initmask(initmask); - bool init0 = true; - bool init1 = true; - State initsel = State::Sx; - if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && ((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && supported_sr) { - // OK - } else if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && (supported_sr & INIT_0)) { - init1 = false; - initsel = State::S0; - } else if (((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && (supported_sr & INIT_1)) { - init0 = false; - initsel = State::S1; - } else if (((supported_cells[FF_ADLATCH0] & initmask) || (supported_cells[FF_ADLATCH1] & flipmask)) && (supported_sr & INIT_1)) { - init1 = false; - initsel = State::S0; - } else if (((supported_cells[FF_ADLATCH1] & initmask) || (supported_cells[FF_ADLATCH0] & flipmask)) && (supported_sr & INIT_0)) { - init0 = false; - initsel = State::S1; - } else { - if (!supported_cells[FF_DLATCHSR]) - reason = "dlatch with async set and reset are not supported"; - else - reason = "initialized dlatch with async set and reset are not supported"; - goto error; - } + } + // Try converting to async load. + if (supported_cells[FF_ALDFF] & initmask) { + ff.arst_to_aload(); + legalize_finish(ff); + return; + } + // Try convertint to SR. + if (supported_cells[FF_DFFSR] & initmask) { + ff.arst_to_sr(); + legalize_finish(ff); + return; + } + ff.add_dummy_ce(); + } + if (supported_cells[FF_ADFFE] & initmask) { + legalize_finish(ff); + return; + } + // Try converting to async load. + if (supported_cells[FF_ALDFFE] & initmask) { + ff.arst_to_aload(); + legalize_finish(ff); + return; + } + // Try convertint to SR. + if (supported_cells[FF_DFFSRE] & initmask) { + ff.arst_to_sr(); + legalize_finish(ff); + return; + } + log_assert(0); + } + + void legalize_aldff(FfData &ff) { + if (!try_flip(ff, supported_aldff)) { + ff.aload_to_sr(); + emulate_split_set_clr(ff); + return; + } + + int initmask = get_initmask(ff); + if (ff.has_ce && !(supported_aldffe & initmask)) { + ff.unmap_ce(); + } - log_warning("Emulating async set + reset with several latches and a mux for %s.%s\n", log_id(cell->module->name), log_id(cell->name)); - initvals.remove_init(sig_q[0]); - Wire *adlatch0_q = cell->module->addWire(NEW_ID); - Wire *adlatch1_q = cell->module->addWire(NEW_ID); - Wire *sel_q = cell->module->addWire(NEW_ID); - if (init0) - initvals.set_init(SigBit(adlatch0_q, 0), initval); - if (init1) - initvals.set_init(SigBit(adlatch1_q, 0), initval); - initvals.set_init(SigBit(sel_q, 0), initsel); - Cell *cell_adlatch0; - Cell *cell_adlatch1; - Cell *cell_sel; - cell_adlatch0 = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_r, sig_d, adlatch0_q, false, !(ff_neg & NEG_E), !(ff_neg & NEG_R)); - cell_adlatch1 = cell->module->addAdlatchGate(NEW_ID, sig_e, sig_s, sig_d, adlatch1_q, true, !(ff_neg & NEG_E), !(ff_neg & NEG_S)); - cell_sel = cell->module->addSrGate(NEW_ID, sig_s, sig_r, sel_q, !(ff_neg & NEG_S), !(ff_neg & NEG_R)); - cell->module->addMuxGate(NEW_ID, adlatch0_q, adlatch1_q, sel_q, sig_q); - - // Bye, cell. - cell->module->remove(cell); - handle_ff(cell_adlatch0); - handle_ff(cell_adlatch1); - handle_ff(cell_sel); + if (!ff.has_ce) { + if (supported_cells[FF_ALDFF] & initmask) { + legalize_finish(ff); return; - } else if (ff_type == FF_SDFF0 || ff_type == FF_SDFF1 || ff_type == FF_SDFFE0 || ff_type == FF_SDFFE1 || ff_type == FF_SDFFCE0 || ff_type == FF_SDFFCE1) { - bool has_set = ff_type == FF_SDFF1 || ff_type == FF_SDFFE1 || ff_type == FF_SDFFCE1; - bool has_en = ff_type == FF_SDFFE0 || ff_type == FF_SDFFE1; - bool has_ce = ff_type == FF_SDFFCE0 || ff_type == FF_SDFFCE1; - - if (has_en) { - if (kill_ce || kill_srst) { - ff_type = has_set ? FF_SDFF1 : FF_SDFF0; - goto unmap_enable; - } - } else if (has_ce) { - if (kill_ce || kill_srst) - goto unmap_srst; - } else { - log_assert(!kill_ce); - if (kill_srst) - goto unmap_srst; - } + } + if (supported_cells[FF_DFFSR] & initmask) { + ff.aload_to_sr(); + legalize_finish(ff); + return; + } + ff.add_dummy_ce(); + } + if (supported_cells[FF_ALDFFE] & initmask) { + legalize_finish(ff); + return; + } + if (supported_cells[FF_DFFSRE] & initmask) { + ff.aload_to_sr(); + legalize_finish(ff); + return; + } + log_assert(0); + } - if (!has_ce) { - if (!has_en && (supported_cells[has_set ? FF_SDFFE1 : FF_SDFFE0] & initmask)) { - // Just add enable. - sig_e = State::S1; - ff_type = has_set ? FF_SDFFE1 : FF_SDFFE0; - break; - } - if (!has_en && (supported_cells[has_set ? FF_SDFFCE1 : FF_SDFFCE0] & initmask)) { - // Just add enable. - sig_e = State::S1; - ff_type = has_set ? FF_SDFFCE1 : FF_SDFFCE0; - break; - } - if (has_en && (supported_cells[has_set ? FF_SDFFCE1 : FF_SDFFCE0] & initmask)) { - // Convert sdffe to sdffce - if (!(ff_neg & NEG_E)) { - if (!(ff_neg & NEG_R)) - sig_e = cell->module->OrGate(NEW_ID, sig_e, sig_r); - else - sig_e = cell->module->OrnotGate(NEW_ID, sig_e, sig_r); - } else { - if (!(ff_neg & NEG_R)) - sig_e = cell->module->AndnotGate(NEW_ID, sig_e, sig_r); - else - sig_e = cell->module->AndGate(NEW_ID, sig_e, sig_r); - } - ff_type = has_set ? FF_SDFFCE1 : FF_SDFFCE0; - break; - } - if (has_en && (supported_cells[has_set ? FF_SDFF1 : FF_SDFF0] & initmask)) { - // Unmap enable. - ff_type = has_set ? FF_SDFF1 : FF_SDFF0; - goto unmap_enable; - } - log_assert(!((has_set ? supported_sdff1 : supported_sdff0) & initmask)); - } else { - if ((has_set ? supported_sdff1 : supported_sdff0) & initmask) { - // Convert sdffce to sdffe, which may be further converted to sdff. - if (!(ff_neg & NEG_R)) { - if (!(ff_neg & NEG_E)) - sig_r = cell->module->AndGate(NEW_ID, sig_r, sig_e); - else - sig_r = cell->module->AndnotGate(NEW_ID, sig_r, sig_e); - } else { - if (!(ff_neg & NEG_E)) - sig_r = cell->module->OrnotGate(NEW_ID, sig_r, sig_e); - else - sig_r = cell->module->OrGate(NEW_ID, sig_r, sig_e); - } - ff_type = has_set ? FF_SDFFE1 : FF_SDFFE0; - continue; - } + void legalize_dffsr(FfData &ff) { + if (!try_flip(ff, supported_dffsr)) { + emulate_split_set_clr(ff); + return; + } + + int initmask = get_initmask(ff); + if (ff.has_ce && !(supported_cells[FF_DFFSRE] & initmask)) { + ff.unmap_ce(); + } + + if (!ff.has_ce) { + if (supported_cells[FF_DFFSR] & initmask) { + legalize_finish(ff); + return; + } + ff.add_dummy_ce(); + } + + log_assert(supported_cells[FF_DFFSRE] & initmask); + legalize_finish(ff); + } + + void legalize_dlatch(FfData &ff) { + if (!try_flip(ff, supported_dlatch)) { + if (!supported_dlatch) + fail_ff(ff, "D latches are not supported"); + else + fail_ff(ff, "initialized D latches are not supported"); + } + + int initmask = get_initmask(ff); + // Some DLATCH is supported with this init val. Just pick a type. + if (supported_cells[FF_DLATCH] & initmask) { + legalize_finish(ff); + } else if (supported_cells[FF_ADLATCH] & initmask) { + ff.add_dummy_arst(); + legalize_finish(ff); + } else if (supported_cells[FF_DLATCHSR] & initmask) { + ff.add_dummy_sr(); + legalize_finish(ff); + } else if (supported_cells[FF_ALDFF] & initmask) { + ff.add_dummy_clk(); + legalize_finish(ff); + } else if (supported_cells[FF_ALDFFE] & initmask) { + ff.add_dummy_clk(); + ff.add_dummy_ce(); + legalize_finish(ff); + } else if (supported_sr & initmask) { + ff.aload_to_sr(); + legalize_sr(ff); + } else { + log_assert(0); + } + } + + void legalize_adlatch(FfData &ff) { + if (!try_flip(ff, supported_adlatch)) { + if (!supported_adlatch) + fail_ff(ff, "D latches with async set or reset are not supported"); + if (!(supported_dlatch & (INIT_0 | INIT_1))) + fail_ff(ff, "initialized D latches are not supported"); + + // If we got here, initialized dlatch is supported, but not this + // particular reset+init combination (nor its negation). + // The only hope left is breaking down to adlatch + dlatch + dlatch + mux. + + if (ff.cell) + log_warning("Emulating mismatched async reset and init with several latches and a mux for %s.%s\n", log_id(ff.module->name), log_id(ff.cell->name)); + ff.remove(); + + emulate_split_init_arst(ff); + return; + } + int initmask = get_initmask(ff); + if (supported_cells[FF_ADLATCH] & initmask) { + // OK + } else if (supported_cells[FF_DLATCHSR] & initmask) { + ff.arst_to_sr(); + } else { + log_assert(0); + } + legalize_finish(ff); + } + + void legalize_dlatchsr(FfData &ff) { + if (!try_flip(ff, supported_cells[FF_DLATCHSR])) { + emulate_split_set_clr(ff); + return; + } + legalize_finish(ff); + } + + void legalize_rlatch(FfData &ff) { + if (!try_flip(ff, supported_rlatch)) { + if (!supported_dlatch) + fail_ff(ff, "D latches are not supported"); + else + fail_ff(ff, "initialized D latches are not supported"); + } + + int initmask = get_initmask(ff); + if (((supported_dlatch_plain & 7) * 0x111) & initmask) { + ff.arst_to_aload(); + legalize_dlatch(ff); + } else if (supported_sr & initmask) { + ff.arst_to_sr(); + legalize_sr(ff); + } else if (supported_adff & initmask) { + ff.add_dummy_clk(); + legalize_adff(ff); + } else { + log_assert(0); + } + } + + void legalize_sr(FfData &ff) { + if (!try_flip(ff, supported_sr)) { + if (!supported_sr) + fail_ff(ff, "sr latches are not supported"); + else + fail_ff(ff, "initialized sr latches are not supported"); + } + int initmask = get_initmask(ff); + if (supported_cells[FF_SR] & initmask) { + // OK + } else if (supported_cells[FF_DLATCHSR] & initmask) { + // Upgrade to DLATCHSR. + ff.add_dummy_aload(); + } else if (supported_cells[FF_DFFSR] & initmask) { + // Upgrade to DFFSR. + ff.add_dummy_clk(); + } else if (supported_cells[FF_DFFSRE] & initmask) { + // Upgrade to DFFSRE. + ff.add_dummy_clk(); + ff.add_dummy_ce(); + } else if (supported_cells[FF_ADLATCH] & (initmask << 4)) { + ff.has_sr = false; + ff.has_aload = true; + ff.has_arst = true; + ff.pol_arst = ff.pol_clr; + ff.sig_arst = ff.sig_clr; + ff.sig_aload = ff.sig_set; + ff.pol_aload = ff.pol_set; + ff.sig_ad = State::S1; + ff.val_arst = State::S0; + } else if (supported_cells[FF_ADLATCH] & (flip_initmask(initmask) << 8)) { + ff.has_sr = false; + ff.has_aload = true; + ff.has_arst = true; + ff.pol_arst = ff.pol_clr; + ff.sig_arst = ff.sig_clr; + ff.sig_aload = ff.sig_set; + ff.pol_aload = ff.pol_set; + ff.sig_ad = State::S0; + ff.val_arst = State::S1; + ff.remove_init(); + Wire *new_q = ff.module->addWire(NEW_ID); + if (ff.is_fine) + ff.module->addNotGate(NEW_ID, new_q, ff.sig_q); + else + ff.module->addNot(NEW_ID, new_q, ff.sig_q); + ff.sig_q = new_q; + if (ff.val_init == State::S0) + ff.val_init = State::S1; + else if (ff.val_init == State::S1) + ff.val_init = State::S0; + } else { + log_assert(0); + } + legalize_finish(ff); + } + + void fixup_reset_x(FfData &ff, int supported) { + for (int i = 0; i < ff.width; i++) { + int mask; + if (ff.val_init[i] == State::S0) + mask = INIT_0; + else if (ff.val_init[i] == State::S1) + mask = INIT_1; + else + mask = INIT_X; + if (ff.has_arst) { + if (ff.val_arst[i] == State::Sx) { + if (!(supported & (mask << 8))) + ff.val_arst[i] = State::S0; + if (!(supported & (mask << 4))) + ff.val_arst[i] = State::S1; } - // Alright, so this particular combination of initval and - // resetval is not natively supported. First, try flipping - // them both to see whether this helps. - if ((has_set ? supported_sdff0 : supported_sdff1) & flip_initmask(initmask)) { - // Checks out, do it. - ff_type = has_ce ? (has_set ? FF_SDFFCE0 : FF_SDFFCE1) : has_en ? (has_set ? FF_SDFFE0 : FF_SDFFE1) : (has_set ? FF_SDFF0 : FF_SDFF1); - goto flip_dqi; + } + if (ff.has_srst) { + if (ff.val_srst[i] == State::Sx) { + if (!(supported & (mask << 8))) + ff.val_srst[i] = State::S0; + if (!(supported & (mask << 4))) + ff.val_srst[i] = State::S1; } + } + } + } - // Nope. No way to get SDFF* of the right kind, so unmap it. - // For SDFFE, the enable has to be unmapped first. - if (has_en) { - ff_type = has_set ? FF_SDFF1 : FF_SDFF0; - goto unmap_enable; - } -unmap_srst: - if (has_ce) - ff_type = FF_DFFE; - else - ff_type = FF_DFF; - if (ff_neg & NEG_R) - sig_d = cell->module->MuxGate(NEW_ID, has_set ? State::S1 : State::S0, sig_d[0], sig_r[0]); + void legalize_ff(FfData &ff) { + if (ff.has_gclk) + return; + + // TODO: consider supporting coarse as well. + if (!ff.is_fine) + return; + + if (mince && ff.has_ce && ff.sig_ce[0].wire && ce_used[ff.sig_ce[0]] < mince) + ff.unmap_ce(); + if (minsrst && ff.has_srst && ff.sig_srst[0].wire && srst_used[ff.sig_srst[0]] < minsrst) + ff.unmap_srst(); + + if (ff.has_clk) { + if (ff.has_sr) { + legalize_dffsr(ff); + } else if (ff.has_aload) { + legalize_aldff(ff); + } else if (ff.has_arst) { + legalize_adff(ff); + } else if (ff.has_srst) { + if (ff.has_ce && ff.ce_over_srst) + legalize_sdffce(ff); else - sig_d = cell->module->MuxGate(NEW_ID, sig_d[0], has_set ? State::S1 : State::S0, sig_r[0]); - ff_neg &= ~NEG_R; - sig_r = SigSpec(); - kill_srst = false; - continue; + legalize_sdff(ff); + } else { + legalize_dff(ff); + } + } else if (ff.has_aload) { + if (ff.has_sr) { + legalize_dlatchsr(ff); + } else if (ff.has_arst) { + legalize_adlatch(ff); + } else { + legalize_dlatch(ff); + } + } else { + if (ff.has_sr) { + legalize_sr(ff); + } else if (ff.has_arst) { + legalize_rlatch(ff); } else { log_assert(0); } } -cell_ok: + } + + void flip_pol(FfData &ff, SigSpec &sig, bool &pol) { + if (sig == State::S0) { + sig = State::S1; + } else if (sig == State::S1) { + sig = State::S0; + } else if (ff.is_fine) { + sig = ff.module->NotGate(NEW_ID, sig); + } else { + sig = ff.module->Not(NEW_ID, sig); + } + pol = !pol; + } + void legalize_finish(FfData &ff) { + int ff_type = get_ff_type(ff); + int initmask = get_initmask(ff); + log_assert(supported_cells[ff_type] & initmask); + int ff_neg = 0; + if (ff.has_sr) { + if (!ff.pol_clr) + ff_neg |= NEG_R; + if (!ff.pol_set) + ff_neg |= NEG_S; + } + if (ff.has_arst) { + if (!ff.pol_arst) + ff_neg |= NEG_R; + } + if (ff.has_srst) { + if (!ff.pol_srst) + ff_neg |= NEG_R; + } + if (ff.has_aload) { + if (!ff.pol_aload) + ff_neg |= NEG_L; + } + if (ff.has_clk) { + if (!ff.pol_clk) + ff_neg |= NEG_C; + } + if (ff.has_ce) { + if (!ff.pol_ce) + ff_neg |= NEG_CE; + } if (!(supported_cells_neg[ff_type][ff_neg] & initmask)) { // Cell is supported, but not with those polarities. // Will need to add some inverters. @@ -921,182 +998,27 @@ cell_ok: if (supported_cells_neg[ff_type][ff_neg ^ xneg] & initmask) break; log_assert(xneg < NUM_NEG); - if (xneg & NEG_R) - sig_r = cell->module->NotGate(NEW_ID, sig_r[0]); - if (xneg & NEG_S) - sig_s = cell->module->NotGate(NEW_ID, sig_s[0]); - if (xneg & NEG_E) - sig_e = cell->module->NotGate(NEW_ID, sig_e[0]); + if (xneg & NEG_CE) + flip_pol(ff, ff.sig_ce, ff.pol_ce); + if (ff.has_sr) { + if (xneg & NEG_R) + flip_pol(ff, ff.sig_clr, ff.pol_clr); + if (xneg & NEG_S) + flip_pol(ff, ff.sig_set, ff.pol_set); + } + if (ff.has_arst && xneg & NEG_R) + flip_pol(ff, ff.sig_arst, ff.pol_arst); + if (ff.has_srst && xneg & NEG_R) + flip_pol(ff, ff.sig_srst, ff.pol_srst); + if (xneg & NEG_L) + flip_pol(ff, ff.sig_aload, ff.pol_aload); if (xneg & NEG_C) - sig_c = cell->module->NotGate(NEW_ID, sig_c[0]); + flip_pol(ff, ff.sig_clk, ff.pol_clk); ff_neg ^= xneg; } - cell->unsetPort(ID::D); - cell->unsetPort(ID::Q); - cell->unsetPort(ID::C); - cell->unsetPort(ID::E); - cell->unsetPort(ID::S); - cell->unsetPort(ID::R); - switch (ff_type) { - case FF_DFF: - cell->type = IdString(stringf("$_DFF_%c_", - (ff_neg & NEG_C) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - break; - case FF_DFFE: - cell->type = IdString(stringf("$_DFFE_%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::E, sig_e); - break; - case FF_ADFF0: - case FF_ADFF1: - cell->type = IdString(stringf("$_DFF_%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_ADFF1) ? '1' : '0' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::R, sig_r); - break; - case FF_ADFFE0: - case FF_ADFFE1: - cell->type = IdString(stringf("$_DFFE_%c%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_ADFFE1) ? '1' : '0', - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::R, sig_r); - break; - case FF_DFFSR: - cell->type = IdString(stringf("$_DFFSR_%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_S) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::S, sig_s); - cell->setPort(ID::R, sig_r); - break; - case FF_DFFSRE: - cell->type = IdString(stringf("$_DFFSRE_%c%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_S) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::S, sig_s); - cell->setPort(ID::R, sig_r); - break; - case FF_SDFF0: - case FF_SDFF1: - cell->type = IdString(stringf("$_SDFF_%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_SDFF1) ? '1' : '0' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::R, sig_r); - break; - case FF_SDFFE0: - case FF_SDFFE1: - cell->type = IdString(stringf("$_SDFFE_%c%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_SDFFE1) ? '1' : '0', - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::R, sig_r); - break; - case FF_SDFFCE0: - case FF_SDFFCE1: - cell->type = IdString(stringf("$_SDFFCE_%c%c%c%c_", - (ff_neg & NEG_C) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_SDFFCE1) ? '1' : '0', - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::C, sig_c); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::R, sig_r); - break; - case FF_DLATCH: - cell->type = IdString(stringf("$_DLATCH_%c_", - (ff_neg & NEG_E) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::E, sig_e); - break; - case FF_ADLATCH0: - case FF_ADLATCH1: - cell->type = IdString(stringf("$_DLATCH_%c%c%c_", - (ff_neg & NEG_E) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P', - (ff_type == FF_ADLATCH1) ? '1' : '0' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::R, sig_r); - break; - case FF_DLATCHSR: - cell->type = IdString(stringf("$_DLATCHSR_%c%c%c_", - (ff_neg & NEG_E) ? 'N' : 'P', - (ff_neg & NEG_S) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P' - )); - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::E, sig_e); - cell->setPort(ID::S, sig_s); - cell->setPort(ID::R, sig_r); - break; - case FF_SR: - cell->type = IdString(stringf("$_SR_%c%c_", - (ff_neg & NEG_S) ? 'N' : 'P', - (ff_neg & NEG_R) ? 'N' : 'P' - )); - cell->setPort(ID::Q, sig_q); - cell->setPort(ID::S, sig_s); - cell->setPort(ID::R, sig_r); - break; - default: - log_assert(0); - } - return; - -error: - log_error("FF %s.%s (type %s) cannot be legalized: %s\n", log_id(cell->module->name), log_id(cell->name), log_id(cell->type), reason); + fixup_reset_x(ff, supported_cells_neg[ff_type][ff_neg]); + ff.emit(); } void execute(std::vector<std::string> args, RTLIL::Design *design) override @@ -1118,79 +1040,83 @@ error: if (args[argidx] == "-cell" && argidx + 2 < args.size()) { std::string celltype = args[++argidx]; std::string inittype = args[++argidx]; - enum FfType ff_type[2] = {NUM_FFTYPES, NUM_FFTYPES}; + enum FfType ff_type; char pol_c = 0; - char pol_e = 0; + char pol_l = 0; char pol_s = 0; char pol_r = 0; + char pol_ce = 0; char srval = 0; if (celltype.substr(0, 5) == "$_SR_" && celltype.size() == 8 && celltype[7] == '_') { - ff_type[0] = FF_SR; + ff_type = FF_SR; pol_s = celltype[5]; pol_r = celltype[6]; } else if (celltype.substr(0, 6) == "$_DFF_" && celltype.size() == 8 && celltype[7] == '_') { - ff_type[0] = FF_DFF; + ff_type = FF_DFF; pol_c = celltype[6]; } else if (celltype.substr(0, 7) == "$_DFFE_" && celltype.size() == 10 && celltype[9] == '_') { - ff_type[0] = FF_DFFE; + ff_type = FF_DFFE; pol_c = celltype[7]; - pol_e = celltype[8]; + pol_ce = celltype[8]; } else if (celltype.substr(0, 6) == "$_DFF_" && celltype.size() == 10 && celltype[9] == '_') { - ff_type[0] = FF_ADFF0; - ff_type[1] = FF_ADFF1; + ff_type = FF_ADFF; pol_c = celltype[6]; pol_r = celltype[7]; srval = celltype[8]; } else if (celltype.substr(0, 7) == "$_DFFE_" && celltype.size() == 12 && celltype[11] == '_') { - ff_type[0] = FF_ADFFE0; - ff_type[1] = FF_ADFFE1; + ff_type = FF_ADFFE; pol_c = celltype[7]; pol_r = celltype[8]; srval = celltype[9]; - pol_e = celltype[10]; + pol_ce = celltype[10]; + } else if (celltype.substr(0, 8) == "$_ALDFF_" && celltype.size() == 11 && celltype[10] == '_') { + ff_type = FF_ALDFF; + pol_c = celltype[8]; + pol_l = celltype[9]; + } else if (celltype.substr(0, 9) == "$_ALDFFE_" && celltype.size() == 13 && celltype[12] == '_') { + ff_type = FF_ALDFFE; + pol_c = celltype[9]; + pol_l = celltype[10]; + pol_ce = celltype[11]; } else if (celltype.substr(0, 8) == "$_DFFSR_" && celltype.size() == 12 && celltype[11] == '_') { - ff_type[0] = FF_DFFSR; + ff_type = FF_DFFSR; pol_c = celltype[8]; pol_s = celltype[9]; pol_r = celltype[10]; } else if (celltype.substr(0, 9) == "$_DFFSRE_" && celltype.size() == 14 && celltype[13] == '_') { - ff_type[0] = FF_DFFSRE; + ff_type = FF_DFFSRE; pol_c = celltype[9]; pol_s = celltype[10]; pol_r = celltype[11]; - pol_e = celltype[12]; + pol_ce = celltype[12]; } else if (celltype.substr(0, 7) == "$_SDFF_" && celltype.size() == 11 && celltype[10] == '_') { - ff_type[0] = FF_SDFF0; - ff_type[1] = FF_SDFF1; + ff_type = FF_SDFF; pol_c = celltype[7]; pol_r = celltype[8]; srval = celltype[9]; } else if (celltype.substr(0, 8) == "$_SDFFE_" && celltype.size() == 13 && celltype[12] == '_') { - ff_type[0] = FF_SDFFE0; - ff_type[1] = FF_SDFFE1; + ff_type = FF_SDFFE; pol_c = celltype[8]; pol_r = celltype[9]; srval = celltype[10]; - pol_e = celltype[11]; + pol_ce = celltype[11]; } else if (celltype.substr(0, 9) == "$_SDFFCE_" && celltype.size() == 14 && celltype[13] == '_') { - ff_type[0] = FF_SDFFCE0; - ff_type[1] = FF_SDFFCE1; + ff_type = FF_SDFFCE; pol_c = celltype[9]; pol_r = celltype[10]; srval = celltype[11]; - pol_e = celltype[12]; + pol_ce = celltype[12]; } else if (celltype.substr(0, 9) == "$_DLATCH_" && celltype.size() == 11 && celltype[10] == '_') { - ff_type[0] = FF_DLATCH; - pol_e = celltype[9]; + ff_type = FF_DLATCH; + pol_l = celltype[9]; } else if (celltype.substr(0, 9) == "$_DLATCH_" && celltype.size() == 13 && celltype[12] == '_') { - ff_type[0] = FF_ADLATCH0; - ff_type[1] = FF_ADLATCH1; - pol_e = celltype[9]; + ff_type = FF_ADLATCH; + pol_l = celltype[9]; pol_r = celltype[10]; srval = celltype[11]; } else if (celltype.substr(0, 11) == "$_DLATCHSR_" && celltype.size() == 15 && celltype[14] == '_') { - ff_type[0] = FF_DLATCHSR; - pol_e = celltype[11]; + ff_type = FF_DLATCHSR; + pol_l = celltype[11]; pol_s = celltype[12]; pol_r = celltype[13]; } else { @@ -1201,9 +1127,10 @@ unrecognized: int match = 0; for (auto pair : { std::make_pair(pol_c, NEG_C), - std::make_pair(pol_e, NEG_E), + std::make_pair(pol_l, NEG_L), std::make_pair(pol_s, NEG_S), std::make_pair(pol_r, NEG_R), + std::make_pair(pol_ce, NEG_CE), }) { if (pair.first == 'N') { mask |= pair.second; @@ -1214,40 +1141,33 @@ unrecognized: goto unrecognized; } } + int initmask; + if (inittype == "x") { + initmask = 0x111; + } else if (inittype == "0") { + initmask = 0x333; + } else if (inittype == "1") { + initmask = 0x555; + } else if (inittype == "r") { + if (srval == 0) + log_error("init type r not valid for cell type %s.\n", celltype.c_str()); + initmask = 0x537; + } else if (inittype == "01") { + initmask = 0x777; + } else { + log_error("unrecognized init type %s for cell type %s.\n", inittype.c_str(), celltype.c_str()); + } if (srval == '0') { - ff_type[1] = NUM_FFTYPES; + initmask &= 0x0ff; } else if (srval == '1') { - ff_type[0] = NUM_FFTYPES; + initmask &= 0xf0f; } else if (srval != 0 && srval != '?') { goto unrecognized; } - for (int i = 0; i < 2; i++) { - if (ff_type[i] == NUM_FFTYPES) - continue; - int initmask; - if (inittype == "x") { - initmask = INIT_X; - } else if (inittype == "0") { - initmask = INIT_X | INIT_0; - } else if (inittype == "1") { - initmask = INIT_X | INIT_1; - } else if (inittype == "r") { - if (srval == 0) - log_error("init type r not valid for cell type %s.\n", celltype.c_str()); - if (i == 0) - initmask = INIT_X | INIT_0; - else - initmask = INIT_X | INIT_1; - } else if (inittype == "01") { - initmask = INIT_X | INIT_0 | INIT_1; - } else { - log_error("unrecognized init type %s for cell type %s.\n", inittype.c_str(), celltype.c_str()); - } - for (int neg = 0; neg < NUM_NEG; neg++) - if ((neg & mask) == match) - supported_cells_neg[ff_type[i]][neg] |= initmask; - supported_cells[ff_type[i]] |= initmask; - } + for (int neg = 0; neg < NUM_NEG; neg++) + if ((neg & mask) == match) + supported_cells_neg[ff_type][neg] |= initmask; + supported_cells[ff_type] |= initmask; continue; } else if (args[argidx] == "-mince" && argidx + 1 < args.size()) { mince = atoi(args[++argidx].c_str()); @@ -1260,13 +1180,21 @@ unrecognized: } extra_args(args, argidx, design); supported_dffsr = supported_cells[FF_DFFSR] | supported_cells[FF_DFFSRE]; - supported_adff0 = supported_cells[FF_ADFF0] | supported_cells[FF_ADFFE0] | supported_dffsr; - supported_adff1 = supported_cells[FF_ADFF1] | supported_cells[FF_ADFFE1] | supported_dffsr; - supported_sdff0 = supported_cells[FF_SDFF0] | supported_cells[FF_SDFFE0] | supported_cells[FF_SDFFCE0]; - supported_sdff1 = supported_cells[FF_SDFF1] | supported_cells[FF_SDFFE1] | supported_cells[FF_SDFFCE1]; - supported_dff = supported_cells[FF_DFF] | supported_cells[FF_DFFE] | supported_dffsr | supported_adff0 | supported_adff1 | supported_sdff0 | supported_sdff1; - supported_sr = supported_dffsr | supported_cells[FF_DLATCHSR] | supported_cells[FF_SR] | supported_cells[FF_ADLATCH0] | flip_initmask(supported_cells[FF_ADLATCH1]); - supported_dlatch = supported_cells[FF_DLATCH] | supported_cells[FF_ADLATCH0] | supported_cells[FF_ADLATCH1] | supported_cells[FF_DLATCHSR]; + supported_aldff = supported_cells[FF_ALDFF] | supported_cells[FF_ALDFFE] | supported_dffsr; + supported_aldffe = supported_cells[FF_ALDFFE] | supported_cells[FF_DFFSRE]; + supported_adff = supported_cells[FF_ADFF] | supported_cells[FF_ADFFE] | supported_dffsr | supported_aldff; + supported_adffe = supported_cells[FF_ADFFE] | supported_cells[FF_ALDFFE] | supported_cells[FF_DFFSRE]; + supported_sdff = supported_cells[FF_SDFF] | supported_cells[FF_SDFFE] | supported_cells[FF_SDFFCE]; + supported_dff = supported_cells[FF_DFF] | supported_cells[FF_DFFE] | supported_adff | supported_sdff; + supported_dffe = supported_cells[FF_DFFE] | supported_cells[FF_DFFSRE] | supported_cells[FF_ALDFFE] | supported_cells[FF_ADFFE] | supported_cells[FF_SDFFE] | supported_cells[FF_SDFFCE]; + supported_sr_plain = supported_dffsr | supported_cells[FF_DLATCHSR] | supported_cells[FF_SR]; + supported_sr = supported_sr_plain; + supported_sr |= (supported_cells[FF_ADLATCH] >> 4 & 7) * 0x111; + supported_sr |= (flip_initmask(supported_cells[FF_ADLATCH]) >> 4 & 7) * 0x111; + supported_dlatch_plain = supported_cells[FF_DLATCH] | supported_cells[FF_ADLATCH] | supported_cells[FF_DLATCHSR] | supported_cells[FF_ALDFF] | supported_cells[FF_ALDFFE]; + supported_dlatch = supported_dlatch_plain | supported_sr_plain; + supported_rlatch = supported_adff | (supported_dlatch & 7) * 0x111; + supported_adlatch = supported_cells[FF_ADLATCH] | supported_cells[FF_DLATCHSR]; for (auto module : design->selected_modules()) { @@ -1281,36 +1209,20 @@ unrecognized: if (!RTLIL::builtin_ff_cell_types().count(cell->type)) continue; - if (cell->hasPort(ID::C) && cell->hasPort(ID::E)) { - SigSpec sig = cell->getPort(ID::E); - // Do not count const enable signals. - if (GetSize(sig) == 1 && sig[0].wire) - ce_used[sig[0]]++; - } - if (cell->type.str().substr(0, 6) == "$_SDFF") { - SigSpec sig = cell->getPort(ID::R); - // Do not count const srst signals. - if (GetSize(sig) == 1 && sig[0].wire) - srst_used[sig[0]]++; - } + FfData ff(&initvals, cell); + if (ff.has_ce && ff.sig_ce[0].wire) + ce_used[ff.sig_ce[0]] += ff.width; + if (ff.has_srst && ff.sig_srst[0].wire) + srst_used[ff.sig_srst[0]] += ff.width; } } - - // First gather FF cells, then iterate over them later. - // We may need to split an FF into several cells. - std::vector<Cell *> ff_cells; - for (auto cell : module->selected_cells()) { - // Early exit for non-FFs. if (!RTLIL::builtin_ff_cell_types().count(cell->type)) continue; - - ff_cells.push_back(cell); + FfData ff(&initvals, cell); + legalize_ff(ff); } - - for (auto cell: ff_cells) - handle_ff(cell); } sigmap.clear(); diff --git a/passes/techmap/dffunmap.cc b/passes/techmap/dffunmap.cc index fb107ff75..7312015f1 100644 --- a/passes/techmap/dffunmap.cc +++ b/passes/techmap/dffunmap.cc @@ -84,21 +84,20 @@ struct DffunmapPass : public Pass { continue; if (ce_only) { - if (!ff.has_en) + if (!ff.has_ce) continue; - ff.unmap_ce(mod); + ff.unmap_ce(); } else if (srst_only) { if (!ff.has_srst) continue; - ff.unmap_srst(mod); + ff.unmap_srst(); } else { - if (!ff.has_en && !ff.has_srst) + if (!ff.has_ce && !ff.has_srst) continue; - ff.unmap_ce_srst(mod); + ff.unmap_ce_srst(); } - mod->remove(cell); - ff.emit(mod, name); + ff.emit(); } } } diff --git a/passes/techmap/extract_reduce.cc b/passes/techmap/extract_reduce.cc index 07b4200cc..892e9a364 100644 --- a/passes/techmap/extract_reduce.cc +++ b/passes/techmap/extract_reduce.cc @@ -152,10 +152,10 @@ struct ExtractReducePass : public Pass log_assert(y.size() == 1); // Should only continue if there is one fanout back into a cell (not to a port) - if (sig_to_sink[y[0]].size() != 1) + if (sig_to_sink[y].size() != 1 || port_sigs.count(y)) break; - x = *sig_to_sink[y[0]].begin(); + x = *sig_to_sink[y].begin(); } sinks.insert(head_cell); @@ -183,13 +183,15 @@ struct ExtractReducePass : public Pass continue; } + auto xy = sigmap(x->getPort(ID::Y)); + //If this signal drives a port, add it to the sinks //(even though it may not be the end of a chain) - if(port_sigs.count(x) && !consumed_cells.count(x)) + if(port_sigs.count(xy) && !consumed_cells.count(x)) sinks.insert(x); //It's a match, search everything out from it - auto& next = sig_to_sink[x]; + auto& next = sig_to_sink[xy]; for(auto z : next) next_loads.insert(z); } @@ -224,89 +226,60 @@ struct ExtractReducePass : public Pass if(consumed_cells.count(head_cell)) continue; - pool<Cell*> cur_supercell; + dict<SigBit, int> sources; + int inner_cells = 0; std::deque<Cell*> bfs_queue = {head_cell}; while (bfs_queue.size()) { Cell* x = bfs_queue.front(); bfs_queue.pop_front(); - cur_supercell.insert(x); + for (auto port: {ID::A, ID::B}) { + auto bit = sigmap(x->getPort(port)[0]); - auto a = sigmap(x->getPort(ID::A)); - log_assert(a.size() == 1); + bool sink_single = sig_to_sink[bit].size() == 1 && !port_sigs.count(bit); - // Must have only one sink unless we're going off chain - // XXX: Check that it is indeed this node? - if( allow_off_chain || (sig_to_sink[a[0]].size() + port_sigs.count(a[0]) == 1) ) - { - Cell* cell_a = sig_to_driver[a[0]]; - if(cell_a && IsRightType(cell_a, gt)) - { - // The cell here is the correct type, and it's definitely driving - // this current cell. - bfs_queue.push_back(cell_a); - } - } + Cell* drv = sig_to_driver[bit]; + bool drv_ok = drv && drv->type == head_cell->type; - auto b = sigmap(x->getPort(ID::B)); - log_assert(b.size() == 1); - - // Must have only one sink - // XXX: Check that it is indeed this node? - if( allow_off_chain || (sig_to_sink[b[0]].size() + port_sigs.count(b[0]) == 1) ) - { - Cell* cell_b = sig_to_driver[b[0]]; - if(cell_b && IsRightType(cell_b, gt)) - { - // The cell here is the correct type, and it's definitely driving only - // this current cell. - bfs_queue.push_back(cell_b); + if (drv_ok && (allow_off_chain || sink_single)) { + inner_cells++; + bfs_queue.push_back(drv); + } else { + sources[bit]++; } } } - log(" Cells:\n"); - for (auto x : cur_supercell) - log(" %s\n", x->name.c_str()); - - if (cur_supercell.size() > 1) + if (inner_cells) { // Worth it to create reduce cell log(" Creating $reduce_* cell!\n"); - pool<SigBit> input_pool; - pool<SigBit> input_pool_intermed; - for (auto x : cur_supercell) - { - input_pool.insert(sigmap(x->getPort(ID::A))[0]); - input_pool.insert(sigmap(x->getPort(ID::B))[0]); - input_pool_intermed.insert(sigmap(x->getPort(ID::Y))[0]); - } - SigSpec input; - for (auto b : input_pool) - if (input_pool_intermed.count(b) == 0) - input.append(b); - SigBit output = sigmap(head_cell->getPort(ID::Y)[0]); - auto new_reduce_cell = module->addCell(NEW_ID, - gt == GateType::And ? ID($reduce_and) : - gt == GateType::Or ? ID($reduce_or) : - gt == GateType::Xor ? ID($reduce_xor) : ""); - new_reduce_cell->setParam(ID::A_SIGNED, 0); - new_reduce_cell->setParam(ID::A_WIDTH, input.size()); - new_reduce_cell->setParam(ID::Y_WIDTH, 1); - new_reduce_cell->setPort(ID::A, input); - new_reduce_cell->setPort(ID::Y, output); - - if(allow_off_chain) - consumed_cells.insert(head_cell); - else - { - for (auto x : cur_supercell) - consumed_cells.insert(x); + SigSpec input; + for (auto it : sources) { + bool cond; + if (head_cell->type == ID($_XOR_)) + cond = it.second & 1; + else + cond = it.second != 0; + if (cond) + input.append(it.first); + } + + if (head_cell->type == ID($_AND_)) { + module->addReduceAnd(NEW_ID, input, output); + } else if (head_cell->type == ID($_OR_)) { + module->addReduceOr(NEW_ID, input, output); + } else if (head_cell->type == ID($_XOR_)) { + module->addReduceXor(NEW_ID, input, output); + } else { + log_assert(false); } + + consumed_cells.insert(head_cell); } } } diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 616fee3f5..7e6df5d2c 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -77,7 +77,7 @@ struct FlattenWorker { bool ignore_wb = false; - void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, std::vector<RTLIL::Cell*> &new_cells) + void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, SigMap &sigmap, std::vector<RTLIL::Cell*> &new_cells) { // Copy the contents of the flattened cell @@ -165,7 +165,6 @@ struct FlattenWorker for (auto bit : tpl_conn.first) tpl_driven.insert(bit); - SigMap sigmap(module); for (auto &port_it : cell->connections()) { IdString port_name = port_it.first; @@ -218,6 +217,7 @@ struct FlattenWorker log_id(module), log_id(cell), log_id(port_it.first), log_signal(new_conn.first), log_signal(new_conn.second)); module->connect(new_conn); + sigmap.add(new_conn.first, new_conn.second); } module->remove(cell); @@ -228,6 +228,7 @@ struct FlattenWorker if (!design->selected(module) || module->get_blackbox_attribute(ignore_wb)) return; + SigMap sigmap(module); std::vector<RTLIL::Cell*> worklist = module->selected_cells(); while (!worklist.empty()) { @@ -251,7 +252,7 @@ struct FlattenWorker // If a design is fully selected and has a top module defined, topological sorting ensures that all cells // added during flattening are black boxes, and flattening is finished in one pass. However, when flattening // individual modules, this isn't the case, and the newly added cells might have to be flattened further. - flatten_cell(design, module, cell, tpl, worklist); + flatten_cell(design, module, cell, tpl, sigmap, worklist); } } }; diff --git a/passes/techmap/iopadmap.cc b/passes/techmap/iopadmap.cc index 45fa5f226..437ad5156 100644 --- a/passes/techmap/iopadmap.cc +++ b/passes/techmap/iopadmap.cc @@ -43,26 +43,28 @@ struct IopadmapPass : public Pass { log("can only map to very simple PAD cells. Use 'techmap' to further map\n"); log("the resulting cells to more sophisticated PAD cells.\n"); log("\n"); - log(" -inpad <celltype> <portname>[:<portname>]\n"); + log(" -inpad <celltype> <in_port>[:<ext_port>]\n"); log(" Map module input ports to the given cell type with the\n"); log(" given output port name. if a 2nd portname is given, the\n"); - log(" signal is passed through the pad call, using the 2nd\n"); + log(" signal is passed through the pad cell, using the 2nd\n"); log(" portname as the port facing the module port.\n"); log("\n"); - log(" -outpad <celltype> <portname>[:<portname>]\n"); - log(" -inoutpad <celltype> <portname>[:<portname>]\n"); + log(" -outpad <celltype> <out_port>[:<ext_port>]\n"); + log(" -inoutpad <celltype> <io_port>[:<ext_port>]\n"); log(" Similar to -inpad, but for output and inout ports.\n"); log("\n"); - log(" -toutpad <celltype> <portname>:<portname>[:<portname>]\n"); + log(" -toutpad <celltype> <oe_port>:<out_port>[:<ext_port>]\n"); log(" Merges $_TBUF_ cells into the output pad cell. This takes precedence\n"); log(" over the other -outpad cell. The first portname is the enable input\n"); - log(" of the tristate driver.\n"); + log(" of the tristate driver, which can be prefixed with `~` for negative\n"); + log(" polarity enable.\n"); log("\n"); - log(" -tinoutpad <celltype> <portname>:<portname>:<portname>[:<portname>]\n"); + log(" -tinoutpad <celltype> <oe_port>:<in_port>:<out_port>[:<ext_port>]\n"); log(" Merges $_TBUF_ cells into the inout pad cell. This takes precedence\n"); log(" over the other -inoutpad cell. The first portname is the enable input\n"); log(" of the tristate driver and the 2nd portname is the internal output\n"); - log(" buffering the external signal.\n"); + log(" buffering the external signal. Like with `-toutpad`, the enable can\n"); + log(" be marked as negative polarity by prefixing the name with `~`.\n"); log("\n"); log(" -ignore <celltype> <portname>[:<portname>]*\n"); log(" Skips mapping inputs/outputs that are already connected to given\n"); @@ -106,6 +108,7 @@ struct IopadmapPass : public Pass { std::string inoutpad_celltype, inoutpad_portname_io, inoutpad_portname_pad; std::string toutpad_celltype, toutpad_portname_oe, toutpad_portname_i, toutpad_portname_pad; std::string tinoutpad_celltype, tinoutpad_portname_oe, tinoutpad_portname_o, tinoutpad_portname_i, tinoutpad_portname_pad; + bool toutpad_neg_oe = false, tinoutpad_neg_oe = false; std::string widthparam, nameparam; pool<pair<IdString, IdString>> ignore; bool flag_bits = false; @@ -137,6 +140,10 @@ struct IopadmapPass : public Pass { toutpad_portname_oe = args[++argidx]; split_portname_pair(toutpad_portname_oe, toutpad_portname_i); split_portname_pair(toutpad_portname_i, toutpad_portname_pad); + if (toutpad_portname_oe[0] == '~') { + toutpad_neg_oe = true; + toutpad_portname_oe = toutpad_portname_oe.substr(1); + } continue; } if (arg == "-tinoutpad" && argidx+2 < args.size()) { @@ -145,6 +152,10 @@ struct IopadmapPass : public Pass { split_portname_pair(tinoutpad_portname_oe, tinoutpad_portname_o); split_portname_pair(tinoutpad_portname_o, tinoutpad_portname_i); split_portname_pair(tinoutpad_portname_i, tinoutpad_portname_pad); + if (tinoutpad_portname_oe[0] == '~') { + tinoutpad_neg_oe = true; + tinoutpad_portname_oe = tinoutpad_portname_oe.substr(1); + } continue; } if (arg == "-ignore" && argidx+2 < args.size()) { @@ -318,6 +329,8 @@ struct IopadmapPass : public Pass { module->uniquify(stringf("$iopadmap$%s.%s[%d]", log_id(module), log_id(wire), i)), RTLIL::escape_id(tinoutpad_celltype)); + if (tinoutpad_neg_oe) + en_sig = module->NotGate(NEW_ID, en_sig); cell->setPort(RTLIL::escape_id(tinoutpad_portname_oe), en_sig); cell->attributes[ID::keep] = RTLIL::Const(1); @@ -340,6 +353,8 @@ struct IopadmapPass : public Pass { module->uniquify(stringf("$iopadmap$%s.%s[%d]", log_id(module), log_id(wire), i)), RTLIL::escape_id(toutpad_celltype)); + if (toutpad_neg_oe) + en_sig = module->NotGate(NEW_ID, en_sig); cell->setPort(RTLIL::escape_id(toutpad_portname_oe), en_sig); cell->setPort(RTLIL::escape_id(toutpad_portname_i), data_sig); cell->attributes[ID::keep] = RTLIL::Const(1); diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index b65224c71..7d8dba439 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -19,6 +19,7 @@ #include "simplemap.h" #include "kernel/sigtools.h" +#include "kernel/ff.h" #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -298,6 +299,30 @@ void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell) } } +void simplemap_bmux(RTLIL::Module *module, RTLIL::Cell *cell) +{ + SigSpec sel = cell->getPort(ID::S); + SigSpec data = cell->getPort(ID::A); + int width = GetSize(cell->getPort(ID::Y)); + + for (int idx = 0; idx < GetSize(sel); idx++) { + SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); + for (int i = 0; i < GetSize(new_data); i += width) { + for (int k = 0; k < width; k++) { + RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); + gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->setPort(ID::A, data[i*2+k]); + gate->setPort(ID::B, data[i*2+width+k]); + gate->setPort(ID::S, sel[idx]); + gate->setPort(ID::Y, new_data[i+k]); + } + } + data = new_data; + } + + module->connect(cell->getPort(ID::Y), data); +} + void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell) { SigSpec lut_ctrl = cell->getPort(ID::A); @@ -305,7 +330,6 @@ void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell) lut_data.extend_u0(1 << cell->getParam(ID::WIDTH).as_int()); for (int idx = 0; GetSize(lut_data) > 1; idx++) { - SigSpec sig_s = lut_ctrl[idx]; SigSpec new_lut_data = module->addWire(NEW_ID, GetSize(lut_data)/2); for (int i = 0; i < GetSize(lut_data); i += 2) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); @@ -367,276 +391,13 @@ void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell) module->connect(RTLIL::SigSig(sig_y, sig_ab)); } -void simplemap_sr(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N'; - char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_s = cell->getPort(ID::SET); - RTLIL::SigSpec sig_r = cell->getPort(ID::CLR); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - std::string gate_type = stringf("$_SR_%c%c_", set_pol, clr_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::S, sig_s[i]); - gate->setPort(ID::R, sig_r[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_ff(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = ID($_FF_); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dff(RTLIL::Module *module, RTLIL::Cell *cell) +void simplemap_ff(RTLIL::Module *, RTLIL::Cell *cell) { - int width = cell->parameters.at(ID::WIDTH).as_int(); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DFF_%c_", clk_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dffe(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_en = cell->getPort(ID::EN); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DFFE_%c%c_", clk_pol, en_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::E, sig_en); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dffsr(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N'; - char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_s = cell->getPort(ID::SET); - RTLIL::SigSpec sig_r = cell->getPort(ID::CLR); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DFFSR_%c%c%c_", clk_pol, set_pol, clr_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::S, sig_s[i]); - gate->setPort(ID::R, sig_r[i]); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dffsre(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N'; - char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N'; - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_s = cell->getPort(ID::SET); - RTLIL::SigSpec sig_r = cell->getPort(ID::CLR); - RTLIL::SigSpec sig_e = cell->getPort(ID::EN); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DFFSRE_%c%c%c%c_", clk_pol, set_pol, clr_pol, en_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::S, sig_s[i]); - gate->setPort(ID::R, sig_r[i]); - gate->setPort(ID::E, sig_e); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_adff_sdff(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - bool is_async = cell->type == ID($adff); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - char rst_pol = cell->parameters.at(is_async ? ID::ARST_POLARITY : ID::SRST_POLARITY).as_bool() ? 'P' : 'N'; - const char *type = is_async ? "DFF" : "SDFF"; - - std::vector<RTLIL::State> rst_val = cell->parameters.at(is_async ? ID::ARST_VALUE : ID::SRST_VALUE).bits; - while (int(rst_val.size()) < width) - rst_val.push_back(RTLIL::State::S0); - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_rst = cell->getPort(is_async ? ID::ARST : ID::SRST); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type_0 = stringf("$_%s_%c%c0_", type, clk_pol, rst_pol); - IdString gate_type_1 = stringf("$_%s_%c%c1_", type, clk_pol, rst_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::R, sig_rst); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_adffe_sdffe_sdffce(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - bool is_async = cell->type == ID($adffe); - char clk_pol = cell->parameters.at(ID::CLK_POLARITY).as_bool() ? 'P' : 'N'; - char rst_pol = cell->parameters.at(is_async ? ID::ARST_POLARITY : ID::SRST_POLARITY).as_bool() ? 'P' : 'N'; - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - const char *type = is_async ? "DFFE" : cell->type == ID($sdffe) ? "SDFFE" : "SDFFCE"; - - std::vector<RTLIL::State> rst_val = cell->parameters.at(is_async ? ID::ARST_VALUE : ID::SRST_VALUE).bits; - while (int(rst_val.size()) < width) - rst_val.push_back(RTLIL::State::S0); - - RTLIL::SigSpec sig_clk = cell->getPort(ID::CLK); - RTLIL::SigSpec sig_rst = cell->getPort(is_async ? ID::ARST : ID::SRST); - RTLIL::SigSpec sig_e = cell->getPort(ID::EN); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type_0 = stringf("$_%s_%c%c0%c_", type, clk_pol, rst_pol, en_pol); - IdString gate_type_1 = stringf("$_%s_%c%c1%c_", type, clk_pol, rst_pol, en_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::C, sig_clk); - gate->setPort(ID::R, sig_rst); - gate->setPort(ID::E, sig_e); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_en = cell->getPort(ID::EN); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DLATCH_%c_", en_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::E, sig_en); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_adlatch(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - char rst_pol = cell->parameters.at(ID::ARST_POLARITY).as_bool() ? 'P' : 'N'; - - std::vector<RTLIL::State> rst_val = cell->parameters.at(ID::ARST_VALUE).bits; - while (int(rst_val.size()) < width) - rst_val.push_back(RTLIL::State::S0); - - RTLIL::SigSpec sig_en = cell->getPort(ID::EN); - RTLIL::SigSpec sig_rst = cell->getPort(ID::ARST); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type_0 = stringf("$_DLATCH_%c%c0_", en_pol, rst_pol); - IdString gate_type_1 = stringf("$_DLATCH_%c%c1_", en_pol, rst_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, rst_val.at(i) == RTLIL::State::S1 ? gate_type_1 : gate_type_0); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::E, sig_en); - gate->setPort(ID::R, sig_rst); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); - } -} - -void simplemap_dlatchsr(RTLIL::Module *module, RTLIL::Cell *cell) -{ - int width = cell->parameters.at(ID::WIDTH).as_int(); - char en_pol = cell->parameters.at(ID::EN_POLARITY).as_bool() ? 'P' : 'N'; - char set_pol = cell->parameters.at(ID::SET_POLARITY).as_bool() ? 'P' : 'N'; - char clr_pol = cell->parameters.at(ID::CLR_POLARITY).as_bool() ? 'P' : 'N'; - - RTLIL::SigSpec sig_en = cell->getPort(ID::EN); - RTLIL::SigSpec sig_s = cell->getPort(ID::SET); - RTLIL::SigSpec sig_r = cell->getPort(ID::CLR); - RTLIL::SigSpec sig_d = cell->getPort(ID::D); - RTLIL::SigSpec sig_q = cell->getPort(ID::Q); - - IdString gate_type = stringf("$_DLATCHSR_%c%c%c_", en_pol, set_pol, clr_pol); - - for (int i = 0; i < width; i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::E, sig_en); - gate->setPort(ID::S, sig_s[i]); - gate->setPort(ID::R, sig_r[i]); - gate->setPort(ID::D, sig_d[i]); - gate->setPort(ID::Q, sig_q[i]); + FfData ff(nullptr, cell); + for (int i = 0; i < ff.width; i++) { + FfData fff = ff.slice({i}); + fff.is_fine = true; + fff.emit(); } } @@ -662,24 +423,27 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($nex)] = simplemap_eqne; mappers[ID($mux)] = simplemap_mux; mappers[ID($tribuf)] = simplemap_tribuf; + mappers[ID($bmux)] = simplemap_bmux; mappers[ID($lut)] = simplemap_lut; mappers[ID($sop)] = simplemap_sop; mappers[ID($slice)] = simplemap_slice; mappers[ID($concat)] = simplemap_concat; - mappers[ID($sr)] = simplemap_sr; + mappers[ID($sr)] = simplemap_ff; mappers[ID($ff)] = simplemap_ff; - mappers[ID($dff)] = simplemap_dff; - mappers[ID($dffe)] = simplemap_dffe; - mappers[ID($dffsr)] = simplemap_dffsr; - mappers[ID($dffsre)] = simplemap_dffsre; - mappers[ID($adff)] = simplemap_adff_sdff; - mappers[ID($sdff)] = simplemap_adff_sdff; - mappers[ID($adffe)] = simplemap_adffe_sdffe_sdffce; - mappers[ID($sdffe)] = simplemap_adffe_sdffe_sdffce; - mappers[ID($sdffce)] = simplemap_adffe_sdffe_sdffce; - mappers[ID($dlatch)] = simplemap_dlatch; - mappers[ID($adlatch)] = simplemap_adlatch; - mappers[ID($dlatchsr)] = simplemap_dlatchsr; + mappers[ID($dff)] = simplemap_ff; + mappers[ID($dffe)] = simplemap_ff; + mappers[ID($dffsr)] = simplemap_ff; + mappers[ID($dffsre)] = simplemap_ff; + mappers[ID($adff)] = simplemap_ff; + mappers[ID($sdff)] = simplemap_ff; + mappers[ID($adffe)] = simplemap_ff; + mappers[ID($sdffe)] = simplemap_ff; + mappers[ID($sdffce)] = simplemap_ff; + mappers[ID($aldff)] = simplemap_ff; + mappers[ID($aldffe)] = simplemap_ff; + mappers[ID($dlatch)] = simplemap_ff; + mappers[ID($adlatch)] = simplemap_ff; + mappers[ID($dlatchsr)] = simplemap_ff; } void simplemap(RTLIL::Module *module, RTLIL::Cell *cell) @@ -712,7 +476,7 @@ struct SimplemapPass : public Pass { log(" $not, $pos, $and, $or, $xor, $xnor\n"); log(" $reduce_and, $reduce_or, $reduce_xor, $reduce_xnor, $reduce_bool\n"); log(" $logic_not, $logic_and, $logic_or, $mux, $tribuf\n"); - log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $sdff, $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n"); + log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $aldff, $aldffe, $sdff, $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n"); log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override diff --git a/passes/techmap/simplemap.h b/passes/techmap/simplemap.h index 03a8fb36f..c7654f68c 100644 --- a/passes/techmap/simplemap.h +++ b/passes/techmap/simplemap.h @@ -34,12 +34,7 @@ extern void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_slice(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_sr(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_dff(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_dffe(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_dffsr(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_adff(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell); +extern void simplemap_ff(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_get_mappers(dict<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers); diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index a69a6d460..5cd78fe28 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -377,10 +377,12 @@ struct TechmapWorker if (c->attributes.count(ID::src)) c->add_strpool_attribute(ID::src, extra_src_attrs); - if (techmap_replace_cell) + if (techmap_replace_cell) { for (auto attr : cell->attributes) if (!c->attributes.count(attr.first)) c->attributes[attr.first] = attr.second; + c->attributes.erase(ID::reprocess_after); + } } for (auto &it : tpl->connections()) { diff --git a/passes/techmap/tribuf.cc b/passes/techmap/tribuf.cc index f92b4cdb0..b45cd268a 100644 --- a/passes/techmap/tribuf.cc +++ b/passes/techmap/tribuf.cc @@ -26,10 +26,12 @@ PRIVATE_NAMESPACE_BEGIN struct TribufConfig { bool merge_mode; bool logic_mode; + bool formal_mode; TribufConfig() { merge_mode = false; logic_mode = false; + formal_mode = false; } }; @@ -55,7 +57,7 @@ struct TribufWorker { dict<SigSpec, vector<Cell*>> tribuf_cells; pool<SigBit> output_bits; - if (config.logic_mode) + if (config.logic_mode || config.formal_mode) for (auto wire : module->wires()) if (wire->port_output) for (auto bit : sigmap(wire)) @@ -102,22 +104,54 @@ struct TribufWorker { } } - if (config.merge_mode || config.logic_mode) + if (config.merge_mode || config.logic_mode || config.formal_mode) { for (auto &it : tribuf_cells) { bool no_tribuf = false; - if (config.logic_mode) { + if (config.logic_mode && !config.formal_mode) { no_tribuf = true; for (auto bit : it.first) if (output_bits.count(bit)) no_tribuf = false; } + if (config.formal_mode) + no_tribuf = true; + if (GetSize(it.second) <= 1 && !no_tribuf) continue; + if (config.formal_mode && GetSize(it.second) >= 2) { + for (auto cell : it.second) { + SigSpec others_s; + + for (auto other_cell : it.second) { + if (other_cell == cell) + continue; + else if (other_cell->type == ID($tribuf)) + others_s.append(other_cell->getPort(ID::EN)); + else + others_s.append(other_cell->getPort(ID::E)); + } + + auto cell_s = cell->type == ID($tribuf) ? cell->getPort(ID::EN) : cell->getPort(ID::E); + + auto other_s = module->ReduceOr(NEW_ID, others_s); + + auto conflict = module->And(NEW_ID, cell_s, other_s); + + std::string name = stringf("$tribuf_conflict$%s", log_id(cell->name)); + auto assert_cell = module->addAssert(name, module->Not(NEW_ID, conflict), SigSpec(true)); + + assert_cell->set_src_attribute(cell->get_src_attribute()); + assert_cell->set_bool_attribute(ID::keep); + + module->design->scratchpad_set_bool("tribuf.added_something", true); + } + } + SigSpec pmux_b, pmux_s; for (auto cell : it.second) { if (cell->type == ID($tribuf)) @@ -159,6 +193,11 @@ struct TribufPass : public Pass { log(" convert tri-state buffers that do not drive output ports\n"); log(" to non-tristate logic. this option implies -merge.\n"); log("\n"); + log(" -formal\n"); + log(" convert all tri-state buffers to non-tristate logic and\n"); + log(" add a formal assertion that no two buffers are driving the\n"); + log(" same net simultaneously. this option implies -merge.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { @@ -176,6 +215,10 @@ struct TribufPass : public Pass { config.logic_mode = true; continue; } + if (args[argidx] == "-formal") { + config.formal_mode = true; + continue; + } break; } extra_args(args, argidx, design); diff --git a/passes/techmap/zinit.cc b/passes/techmap/zinit.cc index 7c5b73c90..cc208c516 100644 --- a/passes/techmap/zinit.cc +++ b/passes/techmap/zinit.cc @@ -20,6 +20,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/ffinit.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -60,123 +61,25 @@ struct ZinitPass : public Pass { SigMap sigmap(module); FfInitVals initvals(&sigmap, module); - pool<IdString> dff_types = { - // FIXME: It would appear that supporting - // $dffsr/$_DFFSR_* would require a new - // cell type where S has priority over R - ID($ff), ID($dff), ID($dffe), /*ID($dffsr),*/ ID($adff), ID($adffe), - ID($sdff), ID($sdffe), ID($sdffce), - ID($_FF_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), - /*ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_), - ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_),*/ - ID($_DFF_N_), ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_), - ID($_DFF_P_), ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_), - // Async set/reset - ID($_DFFE_NN0P_), ID($_DFFE_NN1P_), ID($_DFFE_NP0P_), ID($_DFFE_NP1P_), - ID($_DFFE_PN0P_), ID($_DFFE_PN1P_), ID($_DFFE_PP0P_), ID($_DFFE_PP1P_), - ID($_DFFE_NN0N_), ID($_DFFE_NN1N_), ID($_DFFE_NP0N_), ID($_DFFE_NP1N_), - ID($_DFFE_PN0N_), ID($_DFFE_PN1N_), ID($_DFFE_PP0N_), ID($_DFFE_PP1N_), - // Sync set/reset - ID($_SDFF_NN0_), ID($_SDFF_NN1_), ID($_SDFF_NP0_), ID($_SDFF_NP1_), - ID($_SDFF_PN0_), ID($_SDFF_PN1_), ID($_SDFF_PP0_), ID($_SDFF_PP1_), - ID($_SDFFE_NN0P_), ID($_SDFFE_NN1P_), ID($_SDFFE_NP0P_), ID($_SDFFE_NP1P_), - ID($_SDFFE_PN0P_), ID($_SDFFE_PN1P_), ID($_SDFFE_PP0P_), ID($_SDFFE_PP1P_), - ID($_SDFFE_NN0N_), ID($_SDFFE_NN1N_), ID($_SDFFE_NP0N_), ID($_SDFFE_NP1N_), - ID($_SDFFE_PN0N_), ID($_SDFFE_PN1N_), ID($_SDFFE_PP0N_), ID($_SDFFE_PP1N_), - ID($_SDFFCE_NN0P_), ID($_SDFFCE_NN1P_), ID($_SDFFCE_NP0P_), ID($_SDFFCE_NP1P_), - ID($_SDFFCE_PN0P_), ID($_SDFFCE_PN1P_), ID($_SDFFCE_PP0P_), ID($_SDFFCE_PP1P_), - ID($_SDFFCE_NN0N_), ID($_SDFFCE_NN1N_), ID($_SDFFCE_NP0N_), ID($_SDFFCE_NP1N_), - ID($_SDFFCE_PN0N_), ID($_SDFFCE_PN1N_), ID($_SDFFCE_PP0N_), ID($_SDFFCE_PP1N_) - }; - for (auto cell : module->selected_cells()) { - if (!dff_types.count(cell->type)) - continue; - - SigSpec sig_d = sigmap(cell->getPort(ID::D)); - SigSpec sig_q = sigmap(cell->getPort(ID::Q)); - - if (GetSize(sig_d) < 1 || GetSize(sig_q) < 1) + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) continue; - Const initval = initvals(sig_q); - Const newval = initval; - initvals.remove_init(sig_q); - - Wire *initwire = module->addWire(NEW_ID, GetSize(sig_q)); - - for (int i = 0; i < GetSize(initwire); i++) - if (initval[i] == State::S1) - { - sig_d[i] = module->NotGate(NEW_ID, sig_d[i]); - module->addNotGate(NEW_ID, SigSpec(initwire, i), sig_q[i]); - newval[i] = State::S0; - } - else - { - module->connect(sig_q[i], SigSpec(initwire, i)); - if (all_mode) - newval[i] = State::S0; - } - - initvals.set_init(initwire, newval); + FfData ff(&initvals, cell); log("FF init value for cell %s (%s): %s = %s\n", log_id(cell), log_id(cell->type), - log_signal(sig_q), log_signal(initval)); - - cell->setPort(ID::D, sig_d); - cell->setPort(ID::Q, initwire); - - if (cell->type.in(ID($adff), ID($adffe))) { - auto val = cell->getParam(ID::ARST_VALUE); - for (int i = 0; i < GetSize(initwire); i++) - if (initval[i] == State::S1) - val[i] = (val[i] == State::S1 ? State::S0 : State::S1); - cell->setParam(ID::ARST_VALUE, std::move(val)); - } - else if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) { - auto val = cell->getParam(ID::SRST_VALUE); - for (int i = 0; i < GetSize(initwire); i++) - if (initval[i] == State::S1) - val[i] = (val[i] == State::S1 ? State::S0 : State::S1); - cell->setParam(ID::SRST_VALUE, std::move(val)); - } - else if (initval == State::S1) { - std::string t = cell->type.str(); - if (cell->type.in(ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_), - ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_))) - { - t[8] = (t[8] == '0' ? '1' : '0'); - } - else if (cell->type.in(ID($_SDFF_NN0_), ID($_SDFF_NN1_), ID($_SDFF_NP0_), ID($_SDFF_NP1_), - ID($_SDFF_PN0_), ID($_SDFF_PN1_), ID($_SDFF_PP0_), ID($_SDFF_PP1_))) - { - t[9] = (t[9] == '0' ? '1' : '0'); - } - else if (cell->type.in(ID($_DFFE_NN0P_), ID($_DFFE_NN1P_), ID($_DFFE_NP0P_), ID($_DFFE_NP1P_), - ID($_DFFE_PN0P_), ID($_DFFE_PN1P_), ID($_DFFE_PP0P_), ID($_DFFE_PP1P_), - ID($_DFFE_NN0N_), ID($_DFFE_NN1N_), ID($_DFFE_NP0N_), ID($_DFFE_NP1N_), - ID($_DFFE_PN0N_), ID($_DFFE_PN1N_), ID($_DFFE_PP0N_), ID($_DFFE_PP1N_))) - { - t[9] = (t[9] == '0' ? '1' : '0'); - } - else if (cell->type.in(ID($_SDFFE_NN0P_), ID($_SDFFE_NN1P_), ID($_SDFFE_NP0P_), ID($_SDFFE_NP1P_), - ID($_SDFFE_PN0P_), ID($_SDFFE_PN1P_), ID($_SDFFE_PP0P_), ID($_SDFFE_PP1P_), - ID($_SDFFE_NN0N_), ID($_SDFFE_NN1N_), ID($_SDFFE_NP0N_), ID($_SDFFE_NP1N_), - ID($_SDFFE_PN0N_), ID($_SDFFE_PN1N_), ID($_SDFFE_PP0N_), ID($_SDFFE_PP1N_))) - { - t[10] = (t[10] == '0' ? '1' : '0'); - } - else if (cell->type.in(ID($_SDFFCE_NN0P_), ID($_SDFFCE_NN1P_), ID($_SDFFCE_NP0P_), ID($_SDFFCE_NP1P_), - ID($_SDFFCE_PN0P_), ID($_SDFFCE_PN1P_), ID($_SDFFCE_PP0P_), ID($_SDFFCE_PP1P_), - ID($_SDFFCE_NN0N_), ID($_SDFFCE_NN1N_), ID($_SDFFCE_NP0N_), ID($_SDFFCE_NP1N_), - ID($_SDFFCE_PN0N_), ID($_SDFFCE_PN1N_), ID($_SDFFCE_PP0N_), ID($_SDFFCE_PP1N_))) - { - t[11] = (t[11] == '0' ? '1' : '0'); - } - cell->type = t; + log_signal(ff.sig_q), log_signal(ff.val_init)); + + pool<int> bits; + for (int i = 0; i < ff.width; i++) { + if (ff.val_init.bits[i] == State::S1) + bits.insert(i); + else if (ff.val_init.bits[i] != State::S0 && all_mode) + ff.val_init.bits[i] = State::S0; } + ff.flip_bits(bits); + ff.emit(); } } } diff --git a/passes/tests/test_cell.cc b/passes/tests/test_cell.cc index 4e437e409..e21ec452c 100644 --- a/passes/tests/test_cell.cc +++ b/passes/tests/test_cell.cc @@ -69,6 +69,48 @@ static void create_gold_module(RTLIL::Design *design, RTLIL::IdString cell_type, cell->setPort(ID::Y, wire); } + if (cell_type == ID($bmux)) + { + int width = 1 + xorshift32(8); + int swidth = 1 + xorshift32(4); + + wire = module->addWire(ID::A); + wire->width = width << swidth; + wire->port_input = true; + cell->setPort(ID::A, wire); + + wire = module->addWire(ID::S); + wire->width = swidth; + wire->port_input = true; + cell->setPort(ID::S, wire); + + wire = module->addWire(ID::Y); + wire->width = width; + wire->port_output = true; + cell->setPort(ID::Y, wire); + } + + if (cell_type == ID($demux)) + { + int width = 1 + xorshift32(8); + int swidth = 1 + xorshift32(6); + + wire = module->addWire(ID::A); + wire->width = width; + wire->port_input = true; + cell->setPort(ID::A, wire); + + wire = module->addWire(ID::S); + wire->width = swidth; + wire->port_input = true; + cell->setPort(ID::S, wire); + + wire = module->addWire(ID::Y); + wire->width = width << swidth; + wire->port_output = true; + cell->setPort(ID::Y, wire); + } + if (cell_type == ID($fa)) { int width = 1 + xorshift32(8); @@ -855,8 +897,10 @@ struct TestCellPass : public Pass { cell_types[ID($logic_and)] = "ABSY"; cell_types[ID($logic_or)] = "ABSY"; + cell_types[ID($mux)] = "*"; + cell_types[ID($bmux)] = "*"; + cell_types[ID($demux)] = "*"; if (edges) { - cell_types[ID($mux)] = "*"; cell_types[ID($pmux)] = "*"; } |