diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index 98ddf5aa4..60e03c5a8 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -6,6 +6,7 @@ OBJS += passes/silimate/breaksop.o OBJS += passes/silimate/bus_rebuild.o OBJS += passes/silimate/fanoutbuf.o OBJS += passes/silimate/l2j_frontend.o +OBJS += passes/silimate/mux_push.o OBJS += passes/silimate/obs_clean.o OBJS += passes/silimate/segv.o OBJS += passes/silimate/reg_rename.o diff --git a/passes/silimate/mux_push.cc b/passes/silimate/mux_push.cc new file mode 100644 index 000000000..e1efcd753 --- /dev/null +++ b/passes/silimate/mux_push.cc @@ -0,0 +1,343 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * Abhinav Tondapu + * + * 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/io.h" +#include +#include +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct OptMuxPushWorker +{ + RTLIL::Design *design; + RTLIL::Module *module; + SigMap sigmap; + + dict driver_map; + dict fanout_map; + + pool target_types; + int fanout_limit; + int total_count; + + OptMuxPushWorker(RTLIL::Design *design, RTLIL::Module *module, + const pool &target_types, int fanout_limit) : + design(design), module(module), sigmap(module), + target_types(target_types), fanout_limit(fanout_limit), total_count(0) + { + } + + void build_connectivity() + { + driver_map.clear(); + fanout_map.clear(); + + // Build per-bit driver and fanout maps for the current module + for (auto cell : module->cells()) + { + for (auto &it : cell->connections()) { + RTLIL::SigSpec sig = sigmap(it.second); + if (cell->output(it.first)) { + for (auto &bit : sig) { + if (bit.wire == nullptr) + continue; + auto it_drv = driver_map.find(bit); + if (it_drv == driver_map.end()) { + driver_map[bit] = cell; + } else if (it_drv->second != cell) { + driver_map[bit] = nullptr; + } + } + } + if (cell->input(it.first)) { + for (auto &bit : sig) { + if (bit.wire == nullptr) + continue; + fanout_map[bit]++; + } + } + } + } + + // Treat module output ports as consumers + for (auto wire : module->wires()) { + if (!wire->port_output) + continue; + RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire)); + for (auto &bit : sig) { + if (bit.wire == nullptr) + continue; + fanout_map[bit]++; + } + } + } + + bool sig_has_keep(const RTLIL::SigSpec &sig) + { + for (auto &bit : sig) { + if (bit.wire != nullptr && bit.wire->get_bool_attribute(ID::keep)) + return true; + } + return false; + } + + bool mux_drives_sig(const RTLIL::SigSpec &sig, RTLIL::Cell *&mux_cell) + { + mux_cell = nullptr; + for (auto &bit : sig) { + if (bit.wire == nullptr) + return false; + // Require a single consistent driver for all bits in the SigSpec + auto it_drv = driver_map.find(bit); + if (it_drv == driver_map.end() || it_drv->second == nullptr) + return false; + if (mux_cell == nullptr) + mux_cell = it_drv->second; + else if (mux_cell != it_drv->second) + return false; + } + return mux_cell != nullptr && mux_cell->type == ID($mux); + } + + bool fanout_within_limit(const RTLIL::SigSpec &sig) + { + for (auto &bit : sig) { + if (bit.wire == nullptr) + return false; + // Enforce fanout cap per bit to keep the mux exclusive to this operator + if (fanout_map[bit] > fanout_limit) + return false; + } + return true; + } + + bool fanout_is_one(const RTLIL::SigSpec &sig) + { + for (auto &bit : sig) { + if (bit.wire == nullptr) + return false; + if (fanout_map[bit] != 1) + return false; + } + return true; + } + + void run() + { + while (true) + { + build_connectivity(); + + struct candidate_t { + RTLIL::Cell *cell = nullptr; + RTLIL::Cell *mux_cell = nullptr; + IdString port; + }; + + std::vector candidates; + + for (auto cell : module->selected_cells()) + { + if (!target_types.count(cell->type)) + continue; + if (cell->get_bool_attribute(ID::keep)) + continue; + + RTLIL::SigSpec cell_out = sigmap(cell->getPort(ID::Y)); + if (sig_has_keep(cell_out)) + continue; + + // Look for one mux driven input to push through per operator + for (auto &it : cell->connections()) + { + if (!cell->input(it.first)) + continue; + + RTLIL::SigSpec in_sig = sigmap(it.second); + RTLIL::Cell *mux_cell = nullptr; + if (!mux_drives_sig(in_sig, mux_cell)) + continue; + if (!design->selected(module, mux_cell)) + continue; + if (mux_cell->get_bool_attribute(ID::keep)) + continue; + + RTLIL::SigSpec mux_out = sigmap(mux_cell->getPort(ID::Y)); + // Require the mux to drive the entire operator input + if (mux_out != in_sig) + continue; + if (sig_has_keep(mux_out)) + continue; + if (!fanout_within_limit(mux_out)) + continue; + + // Only push one mux per operator per iteration + candidates.push_back({cell, mux_cell, it.first}); + break; + } + } + + if (candidates.empty()) + break; + + pool cells_to_remove; + pool touched_bits; + + for (auto &cand : candidates) + { + RTLIL::Cell *cell = cand.cell; + RTLIL::Cell *mux_cell = cand.mux_cell; + RTLIL::SigSpec cand_in = sigmap(cell->getPort(cand.port)); + bool overlaps = false; + for (auto &bit : cand_in) { + if (touched_bits.count(bit)) { + overlaps = true; + break; + } + } + if (overlaps) + continue; + // Avoid rewriting overlapping signals within a single iteration + for (auto &bit : cand_in) + touched_bits.insert(bit); + + log_debug(" Pushing mux %s through %s cell %s port %s.\n", + log_id(mux_cell->name), log_id(cell->type), log_id(cell->name), log_id(cand.port)); + + // Reuse the original operator as branch A to preserve the instance name and metadata + RTLIL::Cell *branch_a = cell; + + // Create branch B with a deterministic name derived from the original + RTLIL::IdString branch_b_name = NEW_ID2; + RTLIL::Cell *branch_b = module->addCell(branch_b_name, cell->type); + branch_b->parameters = cell->parameters; + branch_b->attributes = cell->attributes; + branch_b->set_src_attribute(cell->get_src_attribute()); + + RTLIL::SigSpec orig_y = cell->getPort(ID::Y); + std::vector> conns; + for (auto &p : cell->connections()) + conns.push_back(p); + for (auto &p : conns) { + RTLIL::SigSpec conn_sig = p.second; + if (p.first == cand.port) { + branch_a->setPort(p.first, mux_cell->getPort(ID::A)); + branch_b->setPort(p.first, mux_cell->getPort(ID::B)); + } else { + branch_a->setPort(p.first, conn_sig); + branch_b->setPort(p.first, conn_sig); + } + } + + RTLIL::IdString out_a_name = NEW_ID2_SUFFIX("mpa_y"); + RTLIL::IdString out_b_name = NEW_ID2_SUFFIX("mpb_y"); + RTLIL::SigSpec out_a = module->addWire(out_a_name, GetSize(orig_y)); + RTLIL::SigSpec out_b = module->addWire(out_b_name, GetSize(orig_y)); + branch_a->setPort(ID::Y, out_a); + branch_b->setPort(ID::Y, out_b); + branch_a->fixup_parameters(); + branch_b->fixup_parameters(); + + // Always create a new mux so other consumers of the original mux are unaffected + RTLIL::IdString new_mux_name = NEW_ID2_SUFFIX("muxpush"); + RTLIL::Cell *new_mux = module->addMux(new_mux_name, out_a, out_b, mux_cell->getPort(ID::S), orig_y); + new_mux->set_src_attribute(cell->get_src_attribute()); + + // Remove the original mux when it becomes dead after the rewrite + RTLIL::SigSpec mux_out = sigmap(mux_cell->getPort(ID::Y)); + if (fanout_is_one(mux_out)) + cells_to_remove.insert(mux_cell); + + total_count++; + } + + for (auto cell : cells_to_remove) + module->remove(cell); + } + } +}; + +struct OptMuxPushPass : public Pass { + OptMuxPushPass() : Pass("muxpush", "push muxes through lightweight operators") { } + + void help() override + { + log("\n"); + log(" muxpush [options] [selection]\n"); + log("\n"); + log("Push $mux cells forward through lightweight operators by cloning\n"); + log("the operator and re-inserting the mux at the output.\n"); + log("\n"); + log(" -limit \n"); + log(" maximum fanout allowed for the mux output (default: 1)\n"); + log("\n"); + log(" -types \n"); + log(" comma-separated list of operator cell types to push through\n"); + log(" (default: $add,$sub,$xor)\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + int fanout_limit = 1; + std::string types = "$add,$sub,$xor"; + + log_header(design, "Executing MUXPUSH pass (push muxes through light ops).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-limit" && argidx+1 < args.size()) { + fanout_limit = atoi(args[++argidx].c_str()); + continue; + } + if (args[argidx] == "-types" && argidx+1 < args.size()) { + types = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + + pool target_types; + for (auto &tok : split_tokens(types, ", \t\r\n")) { + if (tok.empty()) + continue; + target_types.insert(RTLIL::escape_id(tok)); + } + + int total_count = 0; + for (auto module : design->selected_modules()) { + if (module->get_bool_attribute(ID::blackbox)) + continue; + OptMuxPushWorker worker(design, module, target_types, fanout_limit); + worker.run(); + total_count += worker.total_count; + } + + log(" Pushed muxes through %d operator inputs.\n", total_count); + } +} OptMuxPushPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/silimate/mux_push.ys b/tests/silimate/mux_push.ys new file mode 100644 index 000000000..ac33eb927 --- /dev/null +++ b/tests/silimate/mux_push.ys @@ -0,0 +1,116 @@ +log -header "Interleaved mux/add chain pushes muxes to the end" +log -push +design -reset +read_verilog <1) +equiv_opt -assert muxpush -limit 1 -types $add + +# Mux still drives add inputs due to fanout limit +design -load postopt +select -set add_fanin t:$add %ci* +select -set mux_cells t:$mux t:$ternary +select -assert-count 1 @add_fanin @mux_cells %i + +design -reset +log -pop + + + +log -header "Push with fanout limit > 1" +log -push +design -reset +read_verilog < 1 +design -load postopt +select -set add_fanin t:$add %ci* +select -set mux_cells t:$mux t:$ternary +select -assert-count 0 @add_fanin @mux_cells %i + +design -reset +log -pop