/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * 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 OTHERWISE, 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 OptAndOrPmuxWorker { struct DriverBit { Cell *cell = nullptr; IdString port; int index = -1; }; struct ConsumerBit { Cell *cell = nullptr; IdString port; int index = -1; }; struct EqInfo { SigSpec select; Const value; SigBit bit; }; struct DataExpr { std::vector factors; }; struct Contribution { EqInfo eq; DataExpr data; }; enum TermResult { TERM_FAIL, TERM_ZERO, TERM_OK, }; struct Arm { Const value; SigBit select_bit; std::vector> bits; }; struct BitContribs { int bit_idx = -1; SigSpec select; std::vector contribs; }; struct SelectGroup { SigSpec select; std::vector bits; }; Module *module; SigMap sigmap; dict bit_drivers; dict> bit_consumers; pool observable_bits; pool removed_cells; int converted_count = 0; static const int max_cone_bits = 100000; static const int min_pmux_bits = 8; OptAndOrPmuxWorker(Module *module) : module(module), sigmap(module) { run(); } void build_maps() { bit_drivers.clear(); bit_consumers.clear(); observable_bits.clear(); for (auto cell : module->cells()) { for (auto &conn : cell->connections()) { SigSpec sig = sigmap(conn.second); if (cell->output(conn.first)) { for (int i = 0; i < GetSize(sig); i++) { SigBit bit = sig[i]; if (bit.wire == nullptr) continue; if (bit_drivers.count(bit)) bit_drivers[bit].cell = nullptr; else bit_drivers[bit] = {cell, conn.first, i}; } } if (cell->input(conn.first)) { for (int i = 0; i < GetSize(sig); i++) { SigBit bit = sig[i]; if (bit.wire == nullptr) continue; bit_consumers[bit].push_back({cell, conn.first, i}); } } } } for (auto &conn : module->connections()) { SigSpec lhs = conn.first; SigSpec rhs = sigmap(conn.second); for (int i = 0; i < std::min(GetSize(lhs), GetSize(rhs)); i++) { SigBit lhs_bit = lhs[i]; SigBit rhs_bit = rhs[i]; if (lhs_bit.wire == nullptr) continue; if (lhs_bit.wire->port_output || lhs_bit.wire->get_bool_attribute(ID::keep)) { observable_bits.insert(lhs_bit); observable_bits.insert(rhs_bit); } } } } bool get_driver(SigBit bit, DriverBit &driver) const { bit = sigmap(bit); auto it = bit_drivers.find(bit); if (it == bit_drivers.end() || it->second.cell == nullptr) return false; if (removed_cells.count(it->second.cell)) return false; driver = it->second; return true; } bool get_input_bit(Cell *cell, IdString port, int index, SigBit &bit) const { SigSpec sig = sigmap(cell->getPort(port)); if (index < 0 || index >= GetSize(sig)) return false; bit = sig[index]; return true; } bool bit_has_observable_output(SigBit bit) const { bit = sigmap(bit); if (bit.wire == nullptr) return false; if (bit.wire->port_output || bit.wire->get_bool_attribute(ID::keep)) return true; if (observable_bits.count(bit)) return true; auto it = bit_consumers.find(bit); if (it != bit_consumers.end()) for (auto &consumer : it->second) if (!removed_cells.count(consumer.cell)) return true; return false; } bool match_eq(SigBit bit, EqInfo &eq) const { DriverBit driver; if (!get_driver(bit, driver)) return false; Cell *cell = driver.cell; if (driver.port != ID::Y || driver.index != 0 || cell->type != ID($eq)) return false; SigSpec nonconst_sig = sigmap(cell->getPort(ID::A)); SigSpec const_sig = sigmap(cell->getPort(ID::B)); if (!const_sig.is_fully_const()) { if (!nonconst_sig.is_fully_const()) return false; std::swap(nonconst_sig, const_sig); } if (nonconst_sig.empty() || const_sig.empty() || nonconst_sig.is_fully_const()) return false; eq.select = nonconst_sig; eq.value = const_sig.as_const(); eq.bit = sigmap(bit); return true; } bool collect_or_terms(SigBit bit, std::vector &terms, pool &seen, int &budget) const { if (--budget < 0) return false; bit = sigmap(bit); if (bit == State::S0 || bit == State::Sx || bit == State::Sz) return true; if (bit == State::S1) return false; if (!seen.insert(bit).second) return false; DriverBit driver; if (get_driver(bit, driver) && driver.port == ID::Y && driver.cell->type == ID($or)) { SigBit a, b; if (!get_input_bit(driver.cell, ID::A, driver.index, a)) return false; if (!get_input_bit(driver.cell, ID::B, driver.index, b)) return false; return collect_or_terms(a, terms, seen, budget) && collect_or_terms(b, terms, seen, budget); } terms.push_back(bit); return true; } bool collect_and_factors(SigBit bit, std::vector &factors, pool &seen, int &budget) const { if (--budget < 0) return false; bit = sigmap(bit); if (bit == State::S0 || bit == State::S1 || bit == State::Sx || bit == State::Sz) { factors.push_back(bit); return true; } if (!seen.insert(bit).second) return false; DriverBit driver; if (get_driver(bit, driver) && driver.port == ID::Y && driver.cell->type == ID($and)) { SigBit a, b; if (!get_input_bit(driver.cell, ID::A, driver.index, a)) return false; if (!get_input_bit(driver.cell, ID::B, driver.index, b)) return false; return collect_and_factors(a, factors, seen, budget) && collect_and_factors(b, factors, seen, budget); } factors.push_back(bit); return true; } TermResult parse_term(SigBit bit, Contribution &contrib) const { EqInfo direct_eq; if (match_eq(bit, direct_eq)) { contrib.eq = direct_eq; contrib.data.factors.clear(); return TERM_OK; } std::vector factors; pool seen; int budget = max_cone_bits; if (!collect_and_factors(bit, factors, seen, budget)) return TERM_FAIL; bool have_eq = false; for (auto factor : factors) { factor = sigmap(factor); if (factor == State::S0) return TERM_ZERO; if (factor == State::Sx || factor == State::Sz) return TERM_FAIL; if (factor == State::S1) continue; EqInfo eq; if (match_eq(factor, eq)) { if (!have_eq) { contrib.eq = eq; have_eq = true; } else if (contrib.eq.select != eq.select || contrib.eq.value != eq.value) { return TERM_FAIL; } continue; } contrib.data.factors.push_back(factor); } return have_eq ? TERM_OK : TERM_FAIL; } SigBit make_and_tree(Cell *cell, const std::vector &factors, const std::string &src) { SigBit result = State::S1; for (auto factor : factors) { factor = sigmap(factor); if (factor == State::S0 || factor == State::Sx || factor == State::Sz) return State::S0; if (factor == State::S1) continue; if (result == State::S1) { result = factor; continue; } Wire *wire = module->addWire(NEW_ID2_SUFFIX("andor_pmux_data_and"), 1); module->addAnd(NEW_ID2_SUFFIX("andor_pmux_data_and"), result, factor, SigBit(wire), false, src); result = SigBit(wire); } return result; } SigBit make_or_tree(Cell *cell, const std::vector &terms, const std::string &src) { SigBit result = State::S0; for (auto term : terms) { term = sigmap(term); if (term == State::S1) return State::S1; if (term == State::S0 || term == State::Sx || term == State::Sz) continue; if (result == State::S0) { result = term; continue; } Wire *wire = module->addWire(NEW_ID2_SUFFIX("andor_pmux_data_or"), 1); module->addOr(NEW_ID2_SUFFIX("andor_pmux_data_or"), result, term, SigBit(wire), false, src); result = SigBit(wire); } return result; } SigBit emit_data_bit(Cell *cell, const std::vector &exprs, const std::string &src) { std::vector terms; for (auto &expr : exprs) { SigBit term = make_and_tree(cell, expr.factors, src); if (term == State::S1) return State::S1; if (term != State::S0 && term != State::Sx && term != State::Sz) terms.push_back(term); } return make_or_tree(cell, terms, src); } bool collect_bit_contribs(Cell *cell, int bit_idx, BitContribs &bit_contribs) const { SigSpec y = sigmap(cell->getPort(ID::Y)); SigBit y_bit = y[bit_idx]; if (!bit_has_observable_output(y_bit)) return false; std::vector terms; pool seen; int budget = max_cone_bits; if (!collect_or_terms(y_bit, terms, seen, budget)) return false; bit_contribs.bit_idx = bit_idx; for (auto term : terms) { Contribution contrib; TermResult result = parse_term(term, contrib); if (result == TERM_ZERO) continue; if (result == TERM_FAIL) return false; if (bit_contribs.select.empty()) bit_contribs.select = contrib.eq.select; else if (bit_contribs.select != contrib.eq.select) return false; bit_contribs.contribs.push_back(contrib); } return !bit_contribs.contribs.empty(); } bool convert_group(Cell *cell, const SelectGroup &group, pool &converted_bits) { SigSpec y = sigmap(cell->getPort(ID::Y)); int width = GetSize(group.bits); if (width == 0) return false; std::vector arms; dict arm_index; for (int group_bit_idx = 0; group_bit_idx < width; group_bit_idx++) { const BitContribs &bit_contribs = group.bits[group_bit_idx]; for (auto &contrib : bit_contribs.contribs) { int arm_idx; auto it = arm_index.find(contrib.eq.value); if (it == arm_index.end()) { arm_idx = GetSize(arms); arms.push_back({contrib.eq.value, contrib.eq.bit, std::vector>(width)}); arm_index[contrib.eq.value] = arm_idx; } else { arm_idx = it->second; } arms[arm_idx].bits[group_bit_idx].push_back(contrib.data); } } if (GetSize(arms) < 2 || GetSize(arms) * width < min_pmux_bits) return false; SigSpec pmux_y, pmux_s, pmux_b; std::string src = cell->get_src_attribute(); for (auto &bit_contribs : group.bits) pmux_y.append(y[bit_contribs.bit_idx]); for (auto &arm : arms) { SigSpec data; for (int bit_idx = 0; bit_idx < width; bit_idx++) data.append(emit_data_bit(cell, arm.bits[bit_idx], src)); pmux_b.append(data); pmux_s.append(arm.select_bit); } log("Converting AND/OR mux %s.%s to a $pmux with %d cases and width %d.\n", log_id(module), log_id(cell), GetSize(arms), width); module->addPmux(NEW_ID2_SUFFIX("andor_pmux"), Const(State::S0, width), pmux_b, pmux_s, pmux_y, src); for (auto &bit_contribs : group.bits) converted_bits.insert(bit_contribs.bit_idx); converted_count++; return true; } bool try_convert(Cell *cell) { if (removed_cells.count(cell)) return false; if (cell->type != ID($or) || cell->get_bool_attribute(ID::keep)) return false; SigSpec y = sigmap(cell->getPort(ID::Y)); SigSpec a = sigmap(cell->getPort(ID::A)); SigSpec b = sigmap(cell->getPort(ID::B)); int width = GetSize(y); if (width == 0) return false; std::vector groups; for (int bit_idx = 0; bit_idx < width; bit_idx++) { BitContribs bit_contribs; if (!collect_bit_contribs(cell, bit_idx, bit_contribs)) continue; bool found = false; for (auto &group : groups) { if (group.select == bit_contribs.select) { group.bits.push_back(bit_contribs); found = true; break; } } if (!found) groups.push_back({bit_contribs.select, {bit_contribs}}); } pool converted_bits; for (auto &group : groups) convert_group(cell, group, converted_bits); if (converted_bits.empty()) return false; SigSpec keep_a, keep_b, keep_y; for (int bit_idx = 0; bit_idx < width; bit_idx++) { if (converted_bits.count(bit_idx)) continue; keep_a.append(a[bit_idx]); keep_b.append(b[bit_idx]); keep_y.append(y[bit_idx]); } std::string src = cell->get_src_attribute(); if (!keep_y.empty()) module->addOr(NEW_ID2_SUFFIX("andor_pmux_keep_or"), keep_a, keep_b, keep_y, false, src); removed_cells.insert(cell); module->remove(cell); return true; } void run() { build_maps(); std::vector cells; for (auto cell : module->selected_cells()) cells.push_back(cell); for (auto cell : cells) if (try_convert(cell)) build_maps(); } }; struct OptAndOrPmuxPass : public Pass { OptAndOrPmuxPass() : Pass("opt_andor_pmux", "convert equality-decoded AND/OR muxes to $pmux") { } void help() override { log("\n"); log(" opt_andor_pmux [selection]\n"); log("\n"); log("This pass converts logic of the form:\n"); log("\n"); log(" (sel == C0 & D0) | (sel == C1 & D1) | ...\n"); log("\n"); log("into $pmux cells. It only rewrites terms whose select conditions are\n"); log("equality comparisons against distinct constants of the same select signal.\n"); log("Very small conversions are ignored to avoid replacing tiny boolean cones.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing OPT_ANDOR_PMUX pass (AND/OR muxes to $pmux).\n"); size_t argidx = 1; extra_args(args, argidx, design); int total_converted = 0; for (auto module : design->selected_modules()) { OptAndOrPmuxWorker worker(module); total_converted += worker.converted_count; } if (total_converted) design->scratchpad_set_bool("opt.did_something", true); log("Converted %d AND/OR muxes to $pmux cells.\n", total_converted); } } OptAndOrPmuxPass; PRIVATE_NAMESPACE_END