| /* |
| * yosys -- Yosys Open SYnthesis Suite |
| * |
| * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at> |
| * 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" |
| |
| USING_YOSYS_NAMESPACE |
| PRIVATE_NAMESPACE_BEGIN |
| |
| struct ExclusiveDatabase |
| { |
| Module *module; |
| const SigMap &sigmap; |
| |
| dict<SigBit, std::pair<SigSpec,std::vector<Const>>> sig_cmp_prev; |
| |
| ExclusiveDatabase(Module *module, const SigMap &sigmap) : module(module), sigmap(sigmap) |
| { |
| SigSpec const_sig, nonconst_sig; |
| SigBit y_port; |
| pool<Cell*> reduce_or; |
| for (auto cell : module->cells()) { |
| if (cell->type == ID($eq)) { |
| nonconst_sig = sigmap(cell->getPort(ID::A)); |
| const_sig = sigmap(cell->getPort(ID::B)); |
| if (!const_sig.is_fully_const()) { |
| if (!nonconst_sig.is_fully_const()) |
| continue; |
| std::swap(nonconst_sig, const_sig); |
| } |
| y_port = sigmap(cell->getPort(ID::Y)); |
| } |
| else if (cell->type == ID($logic_not)) { |
| nonconst_sig = sigmap(cell->getPort(ID::A)); |
| const_sig = Const(State::S0, GetSize(nonconst_sig)); |
| y_port = sigmap(cell->getPort(ID::Y)); |
| } |
| else if (cell->type == ID($reduce_or)) { |
| reduce_or.insert(cell); |
| continue; |
| } |
| else continue; |
| |
| log_assert(!nonconst_sig.empty()); |
| log_assert(!const_sig.empty()); |
| sig_cmp_prev[y_port] = std::make_pair(nonconst_sig,std::vector<Const>{const_sig.as_const()}); |
| } |
| |
| for (auto cell : reduce_or) { |
| nonconst_sig = SigSpec(); |
| std::vector<Const> values; |
| SigSpec a_port = sigmap(cell->getPort(ID::A)); |
| for (auto bit : a_port) { |
| auto it = sig_cmp_prev.find(bit); |
| if (it == sig_cmp_prev.end()) { |
| nonconst_sig = SigSpec(); |
| break; |
| } |
| if (nonconst_sig.empty()) |
| nonconst_sig = it->second.first; |
| else if (nonconst_sig != it->second.first) { |
| nonconst_sig = SigSpec(); |
| break; |
| } |
| for (auto value : it->second.second) |
| values.push_back(value); |
| } |
| if (nonconst_sig.empty()) |
| continue; |
| y_port = sigmap(cell->getPort(ID::Y)); |
| sig_cmp_prev[y_port] = std::make_pair(nonconst_sig,std::move(values)); |
| } |
| } |
| |
| bool query(const SigSpec &sig) const |
| { |
| SigSpec nonconst_sig; |
| pool<Const> const_values; |
| |
| for (auto bit : sig.bits()) { |
| auto it = sig_cmp_prev.find(bit); |
| if (it == sig_cmp_prev.end()) |
| return false; |
| |
| if (nonconst_sig.empty()) |
| nonconst_sig = it->second.first; |
| else if (nonconst_sig != it->second.first) |
| return false; |
| |
| for (auto value : it->second.second) |
| if (!const_values.insert(value).second) |
| return false; |
| } |
| |
| return true; |
| } |
| }; |
| |
| |
| struct MuxpackWorker |
| { |
| Module *module; |
| SigMap sigmap; |
| |
| int mux_count, pmux_count; |
| |
| pool<Cell*> remove_cells; |
| |
| dict<SigSpec, Cell*> sig_chain_next; |
| dict<SigSpec, Cell*> sig_chain_prev; |
| pool<SigBit> sigbit_with_non_chain_users; |
| pool<Cell*> chain_start_cells; |
| pool<Cell*> candidate_cells; |
| |
| ExclusiveDatabase excl_db; |
| |
| void make_sig_chain_next_prev() |
| { |
| for (auto wire : module->wires()) |
| { |
| if (wire->port_output || wire->get_bool_attribute(ID::keep)) { |
| for (auto bit : sigmap(wire)) |
| sigbit_with_non_chain_users.insert(bit); |
| } |
| } |
| |
| for (auto cell : module->cells()) |
| { |
| if (cell->type.in(ID($mux), ID($pmux)) && !cell->get_bool_attribute(ID::keep)) |
| { |
| SigSpec a_sig = sigmap(cell->getPort(ID::A)); |
| SigSpec b_sig; |
| if (cell->type == ID($mux)) |
| b_sig = sigmap(cell->getPort(ID::B)); |
| SigSpec y_sig = sigmap(cell->getPort(ID::Y)); |
| |
| if (sig_chain_next.count(a_sig)) |
| for (auto a_bit : a_sig.bits()) |
| sigbit_with_non_chain_users.insert(a_bit); |
| else { |
| sig_chain_next[a_sig] = cell; |
| candidate_cells.insert(cell); |
| } |
| |
| if (!b_sig.empty()) { |
| if (sig_chain_next.count(b_sig)) |
| for (auto b_bit : b_sig.bits()) |
| sigbit_with_non_chain_users.insert(b_bit); |
| else { |
| sig_chain_next[b_sig] = cell; |
| candidate_cells.insert(cell); |
| } |
| } |
| |
| sig_chain_prev[y_sig] = cell; |
| continue; |
| } |
| |
| for (auto conn : cell->connections()) |
| if (cell->input(conn.first)) |
| for (auto bit : sigmap(conn.second)) |
| sigbit_with_non_chain_users.insert(bit); |
| } |
| } |
| |
| void find_chain_start_cells() |
| { |
| for (auto cell : candidate_cells) |
| { |
| log_debug("Considering %s (%s)\n", log_id(cell), log_id(cell->type)); |
| |
| SigSpec a_sig = sigmap(cell->getPort(ID::A)); |
| if (cell->type == ID($mux)) { |
| SigSpec b_sig = sigmap(cell->getPort(ID::B)); |
| if (sig_chain_prev.count(a_sig) + sig_chain_prev.count(b_sig) != 1) |
| goto start_cell; |
| |
| if (!sig_chain_prev.count(a_sig)) |
| a_sig = b_sig; |
| } |
| else if (cell->type == ID($pmux)) { |
| if (!sig_chain_prev.count(a_sig)) |
| goto start_cell; |
| } |
| else log_abort(); |
| |
| for (auto bit : a_sig.bits()) |
| if (sigbit_with_non_chain_users.count(bit)) |
| goto start_cell; |
| |
| { |
| Cell *prev_cell = sig_chain_prev.at(a_sig); |
| log_assert(prev_cell); |
| SigSpec s_sig = sigmap(cell->getPort(ID(S))); |
| s_sig.append(sigmap(prev_cell->getPort(ID(S)))); |
| if (!excl_db.query(s_sig)) |
| goto start_cell; |
| } |
| |
| continue; |
| |
| start_cell: |
| chain_start_cells.insert(cell); |
| } |
| } |
| |
| vector<Cell*> create_chain(Cell *start_cell) |
| { |
| vector<Cell*> chain; |
| |
| Cell *c = start_cell; |
| while (c != nullptr) |
| { |
| chain.push_back(c); |
| |
| SigSpec y_sig = sigmap(c->getPort(ID::Y)); |
| |
| if (sig_chain_next.count(y_sig) == 0) |
| break; |
| |
| c = sig_chain_next.at(y_sig); |
| if (chain_start_cells.count(c) != 0) |
| break; |
| } |
| |
| return chain; |
| } |
| |
| void process_chain(vector<Cell*> &chain) |
| { |
| if (GetSize(chain) < 2) |
| return; |
| |
| int cursor = 0; |
| while (cursor < GetSize(chain)) |
| { |
| int cases = GetSize(chain) - cursor; |
| |
| Cell *first_cell = chain[cursor]; |
| dict<int, SigBit> taps_dict; |
| |
| if (cases < 2) { |
| cursor++; |
| continue; |
| } |
| |
| Cell *last_cell = chain[cursor+cases-1]; |
| |
| log("Converting %s.%s ... %s.%s to a pmux with %d cases.\n", |
| log_id(module), log_id(first_cell), log_id(module), log_id(last_cell), cases); |
| |
| mux_count += cases; |
| pmux_count += 1; |
| |
| first_cell->type = ID($pmux); |
| SigSpec b_sig = first_cell->getPort(ID::B); |
| SigSpec s_sig = first_cell->getPort(ID(S)); |
| |
| for (int i = 1; i < cases; i++) { |
| Cell* prev_cell = chain[cursor+i-1]; |
| Cell* cursor_cell = chain[cursor+i]; |
| if (sigmap(prev_cell->getPort(ID::Y)) == sigmap(cursor_cell->getPort(ID::A))) { |
| b_sig.append(cursor_cell->getPort(ID::B)); |
| s_sig.append(cursor_cell->getPort(ID(S))); |
| } |
| else { |
| log_assert(cursor_cell->type == ID($mux)); |
| b_sig.append(cursor_cell->getPort(ID::A)); |
| s_sig.append(module->LogicNot(NEW_ID, cursor_cell->getPort(ID(S)))); |
| } |
| remove_cells.insert(cursor_cell); |
| } |
| |
| first_cell->setPort(ID::B, b_sig); |
| first_cell->setPort(ID(S), s_sig); |
| first_cell->setParam(ID(S_WIDTH), GetSize(s_sig)); |
| first_cell->setPort(ID::Y, last_cell->getPort(ID::Y)); |
| |
| cursor += cases; |
| } |
| } |
| |
| void cleanup() |
| { |
| for (auto cell : remove_cells) |
| module->remove(cell); |
| |
| remove_cells.clear(); |
| sig_chain_next.clear(); |
| sig_chain_prev.clear(); |
| chain_start_cells.clear(); |
| candidate_cells.clear(); |
| } |
| |
| MuxpackWorker(Module *module) : |
| module(module), sigmap(module), mux_count(0), pmux_count(0), excl_db(module, sigmap) |
| { |
| make_sig_chain_next_prev(); |
| find_chain_start_cells(); |
| |
| for (auto c : chain_start_cells) { |
| vector<Cell*> chain = create_chain(c); |
| process_chain(chain); |
| } |
| |
| cleanup(); |
| } |
| }; |
| |
| struct MuxpackPass : public Pass { |
| MuxpackPass() : Pass("muxpack", "$mux/$pmux cascades to $pmux") { } |
| void help() YS_OVERRIDE |
| { |
| // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| |
| log("\n"); |
| log(" muxpack [selection]\n"); |
| log("\n"); |
| log("This pass converts cascaded chains of $pmux cells (e.g. those create from case\n"); |
| log("constructs) and $mux cells (e.g. those created by if-else constructs) into\n"); |
| log("$pmux cells.\n"); |
| log("\n"); |
| log("This optimisation is conservative --- it will only pack $mux or $pmux cells\n"); |
| log("whose select lines are driven by '$eq' cells with other such cells if it can be\n"); |
| log("certain that their select inputs are mutually exclusive.\n"); |
| log("\n"); |
| } |
| void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE |
| { |
| log_header(design, "Executing MUXPACK pass ($mux cell cascades to $pmux).\n"); |
| |
| size_t argidx; |
| for (argidx = 1; argidx < args.size(); argidx++) |
| { |
| break; |
| } |
| extra_args(args, argidx, design); |
| |
| int mux_count = 0; |
| int pmux_count = 0; |
| |
| for (auto module : design->selected_modules()) { |
| MuxpackWorker worker(module); |
| mux_count += worker.mux_count; |
| pmux_count += worker.pmux_count; |
| } |
| |
| log("Converted %d (p)mux cells into %d pmux cells.\n", mux_count, pmux_count); |
| } |
| } MuxpackPass; |
| |
| PRIVATE_NAMESPACE_END |