diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index 81c93c07b..72a7495c5 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -1416,7 +1416,7 @@ struct HierarchyPass : public Pass { continue; } - Wire *t = module->addWire(NEW_ID, GetSize(c)); + Wire *t = module->addWire(NEW_ID2_SUFFIX("wand_wor"), GetSize(c)); new_sig.append(t); update_port = true; @@ -1440,24 +1440,25 @@ struct HierarchyPass : public Pass { { bool wand = wand_map.count(w); SigSpec sigs = wand ? wand_map.at(w) : wor_map.at(w); + IdString name = w->name; if (GetSize(sigs) == 0) continue; if (GetSize(w) == 1) { if (wand) - module->addReduceAnd(NEW_ID, sigs, w); + module->addReduceAnd(NEW_ID4_SUFFIX("reduce_and"), sigs, w); else - module->addReduceOr(NEW_ID, sigs, w); + module->addReduceOr(NEW_ID4_SUFFIX("reduce_or"), sigs, w); continue; } SigSpec s = sigs.extract(0, GetSize(w)); for (int i = GetSize(w); i < GetSize(sigs); i += GetSize(w)) { if (wand) - s = module->And(NEW_ID, s, sigs.extract(i, GetSize(w))); + s = module->And(NEW_ID4_SUFFIX("and"), s, sigs.extract(i, GetSize(w))); else - s = module->Or(NEW_ID, s, sigs.extract(i, GetSize(w))); + s = module->Or(NEW_ID4_SUFFIX("or"), s, sigs.extract(i, GetSize(w))); } module->connect(w, s); } @@ -1519,7 +1520,7 @@ struct HierarchyPass : public Pass { if (w->port_input && !w->port_output) sig.extend_u0(GetSize(w), sig.is_wire() && sig.as_wire()->is_signed); else - sig.append(module->addWire(NEW_ID, n)); + sig.append(module->addWire(NEW_ID2_SUFFIX("port_resize"), n)); } if (!conn.second.is_fully_const() || !w->port_input || w->port_output) diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 327c6bfc4..220f0d9f8 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -22,6 +22,7 @@ OBJS += passes/opt/opt_lut_ins.o OBJS += passes/opt/opt_ffinv.o OBJS += passes/opt/pmux2shiftx.o OBJS += passes/opt/muxpack.o +OBJS += passes/opt/opt_andor_pmux.o OBJS += passes/opt/opt_balance_tree.o OBJS += passes/opt/opt_parallel_prefix.o OBJS += passes/opt/opt_prienc.o diff --git a/passes/opt/opt_andor_pmux.cc b/passes/opt/opt_andor_pmux.cc new file mode 100644 index 000000000..d034ed236 --- /dev/null +++ b/passes/opt/opt_andor_pmux.cc @@ -0,0 +1,492 @@ +/* + * 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; + }; + + Module *module; + SigMap sigmap; + + dict bit_drivers; + dict> bit_consumers; + pool removed_cells; + + int converted_count = 0; + static const int max_cone_bits = 100000; + + OptAndOrPmuxWorker(Module *module) : module(module), sigmap(module) + { + run(); + } + + void build_maps() + { + bit_drivers.clear(); + bit_consumers.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}); + } + } + } + } + } + + 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 feeds_or(Cell *cell) const + { + SigSpec y = sigmap(cell->getPort(ID::Y)); + for (auto bit : y) { + if (bit.wire == nullptr) + continue; + auto it = bit_consumers.find(bit); + if (it == bit_consumers.end()) + continue; + for (auto &consumer : it->second) + if (!removed_cells.count(consumer.cell) && consumer.cell != cell && consumer.cell->type == ID($or)) + return true; + } + return false; + } + + bool has_observable_output(Cell *cell) const + { + SigSpec y = sigmap(cell->getPort(ID::Y)); + for (auto bit : y) { + if (bit.wire == nullptr) + continue; + if (bit.wire->port_output || bit.wire->get_bool_attribute(ID::keep)) + 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 try_convert(Cell *cell) + { + if (removed_cells.count(cell)) + return false; + if (cell->type != ID($or) || cell->get_bool_attribute(ID::keep)) + return false; + if (feeds_or(cell) || !has_observable_output(cell)) + return false; + + SigSpec y = sigmap(cell->getPort(ID::Y)); + int width = GetSize(y); + if (width == 0) + return false; + + SigSpec select; + std::vector arms; + dict arm_index; + + for (int bit_idx = 0; bit_idx < width; bit_idx++) + { + std::vector terms; + pool seen; + int budget = max_cone_bits; + if (!collect_or_terms(y[bit_idx], terms, seen, budget)) + return false; + + for (auto term : terms) + { + Contribution contrib; + TermResult result = parse_term(term, contrib); + if (result == TERM_ZERO) + continue; + if (result == TERM_FAIL) + return false; + + if (select.empty()) + select = contrib.eq.select; + else if (select != contrib.eq.select) + return false; + + 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[bit_idx].push_back(contrib.data); + } + } + + if (GetSize(arms) < 2) + return false; + + SigSpec pmux_s, pmux_b; + std::string src = cell->get_src_attribute(); + + 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, cell->getPort(ID::Y), src); + removed_cells.insert(cell); + module->remove(cell); + + converted_count++; + return true; + } + + void run() + { + build_maps(); + + std::vector cells; + for (auto cell : module->selected_cells()) + cells.push_back(cell); + + for (auto cell : cells) + try_convert(cell); + } +}; + +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("\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 diff --git a/passes/opt/opt_hier.cc b/passes/opt/opt_hier.cc index d8855ab56..b354bc434 100644 --- a/passes/opt/opt_hier.cc +++ b/passes/opt/opt_hier.cc @@ -94,6 +94,7 @@ struct ModuleIndex { bool apply_changes(ModuleIndex &parent, Cell *instantiation) { log_assert(instantiation->module == parent.module); + Cell *cell = instantiation; if (module->get_blackbox_attribute()) { // no propagating out of blackboxes @@ -142,7 +143,7 @@ struct ModuleIndex { rhs.replace(constant_outputs); log_assert(rhs.is_fully_const()); parent.module->connect(value.extract(chunk.offset, chunk.width), rhs); - SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("const_output"), chunk.width); + SigSpec dummy = parent.module->addWire(NEW_ID2_SUFFIX("const_output"), chunk.width); for (int i = 0; i < chunk.width; i++) value[chunk.offset + i] = dummy[i]; } @@ -187,7 +188,7 @@ struct ModuleIndex { severed_port_bits.sort_and_unify(); for (auto chunk : severed_port_bits.chunks()) { SigSpec &value = instantiation->connections_.at(chunk.wire->name); - SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("tie_together"), chunk.width); + SigSpec dummy = parent.module->addWire(NEW_ID2_SUFFIX("tie_together"), chunk.width); for (int i = 0; i < chunk.width; i++) value[chunk.offset + i] = dummy[i]; } diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index 2cdc24045..eadfd1375 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -11,6 +11,7 @@ OBJS += passes/silimate/obs_clean.o OBJS += passes/silimate/segv.o OBJS += passes/silimate/reg_rename.o OBJS += passes/silimate/infer_ce.o +OBJS += passes/silimate/ffnormpol.o OBJS += passes/silimate/report_fanout.o OBJS += passes/silimate/splitfanout.o OBJS += passes/silimate/splitlarge.o @@ -20,6 +21,7 @@ OBJS += passes/silimate/cone_partition.o OBJS += passes/silimate/clkmerge.o OBJS += passes/silimate/opt_boundary.o OBJS += passes/silimate/opt_vps.o +OBJS += passes/silimate/infer_icg.o OBJS += passes/silimate/opt_expand.o GENFILES += passes/silimate/peepopt_expand.h diff --git a/passes/silimate/ffnormpol.cc b/passes/silimate/ffnormpol.cc new file mode 100644 index 000000000..34fb16daa --- /dev/null +++ b/passes/silimate/ffnormpol.cc @@ -0,0 +1,135 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2026 Silimate Inc. + * + * 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/ff.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct FfNormPolPass : public Pass { + FfNormPolPass() : Pass("ffnormpol", "normalize FF/latch control polarities to positive") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" ffnormpol [selection]\n"); + log("\n"); + log("This pass normalizes built-in FF and latch control polarities to\n"); + log("positive polarity. Negative-edge clocks and active-low enables,\n"); + log("resets, sets, clears, and latch enables are rewritten by inserting\n"); + log("inverters on the corresponding control signals and re-emitting the\n"); + log("cell with positive polarity.\n"); + log("\n"); + log("Both coarse-grain cells with polarity parameters and fine-grain cells\n"); + log("with polarity encoded in the cell type are handled through FfData.\n"); + log("\n"); + } + + struct Worker { + Module *module; + SigMap sigmap; + FfInitVals initvals; + dict inverted_signals; + int normalized_cells = 0; + int normalized_controls = 0; + + Worker(Module *module) : module(module), sigmap(module), initvals(&sigmap, module) { } + + SigSpec invert(SigSpec sig, const char *suffix, const std::string &src) + { + sig = sigmap(sig); + if (inverted_signals.count(sig)) + return inverted_signals.at(sig); + + SigSpec inv = module->Not(NEW_ID_SUFFIX(suffix), sig, false, src); + inverted_signals[sig] = inv; + return inv; + } + + bool normalize(FfData &ff, SigSpec &sig, bool &pol, const char *suffix) + { + if (pol) + return false; + + sig = invert(sig, suffix, ff.attributes[ID::src].decode_string()); + pol = true; + normalized_controls++; + return true; + } + + void run() + { + std::vector cells; + for (auto cell : module->selected_cells()) + if (cell->is_builtin_ff()) + cells.push_back(cell); + + for (auto cell : cells) { + FfData ff(&initvals, cell); + bool changed = false; + + if (ff.has_clk) + changed |= normalize(ff, ff.sig_clk, ff.pol_clk, "ffnormpol_clk"); + if (ff.has_ce) + changed |= normalize(ff, ff.sig_ce, ff.pol_ce, "ffnormpol_ce"); + if (ff.has_aload) + changed |= normalize(ff, ff.sig_aload, ff.pol_aload, "ffnormpol_aload"); + if (ff.has_arst) + changed |= normalize(ff, ff.sig_arst, ff.pol_arst, "ffnormpol_arst"); + if (ff.has_srst) + changed |= normalize(ff, ff.sig_srst, ff.pol_srst, "ffnormpol_srst"); + if (ff.has_sr) { + changed |= normalize(ff, ff.sig_clr, ff.pol_clr, "ffnormpol_clr"); + changed |= normalize(ff, ff.sig_set, ff.pol_set, "ffnormpol_set"); + } + + if (changed) { + ff.emit(); + normalized_cells++; + } + } + } + }; + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing FFNORMPOL pass (normalize FF/latch control polarities).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + break; + extra_args(args, argidx, design); + + int total_cells = 0; + int total_controls = 0; + for (auto module : design->selected_modules()) { + Worker worker(module); + worker.run(); + total_cells += worker.normalized_cells; + total_controls += worker.normalized_controls; + } + + log("Normalized %d controls on %d FF/latch cells.\n", total_controls, total_cells); + } +} FfNormPolPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/silimate/infer_icg.cc b/passes/silimate/infer_icg.cc new file mode 100644 index 000000000..cb9732906 --- /dev/null +++ b/passes/silimate/infer_icg.cc @@ -0,0 +1,292 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2026 Silimate Inc. + * + * 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 InferIcgPass : public Pass { + InferIcgPass() : Pass("infer_icg", "infer $icg cells from latch-based clock gates") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" infer_icg [selection]\n"); + log("\n"); + log("This pass recognizes latch-based integrated clock gate structures and\n"); + log("replaces the gated-clock output with an internal $icg cell.\n"); + log("\n"); + log("The recognized positive-edge pattern is a one-bit $dlatch that is\n"); + log("transparent while the clock is low, feeding a one-bit $and or $logic_and\n"); + log("with the same clock. The latch can be active-low on the clock or\n"); + log("active-high on an inverted copy of the clock.\n"); + log("\n"); + log("The complementary high-idle pattern is also recognized: a latch that is\n"); + log("transparent while the clock is high, feeding a one-bit $or or $logic_or\n"); + log("with the same clock and an inverted latched enable. This is rewritten as\n"); + log("a $icg on the inverted clock plus an output inverter.\n"); + log("\n"); + log("The scan-enable leg is optional. If the effective latch data is driven\n"); + log("by a one-bit $or or $logic_or, its inputs are used as the $icg EN and\n"); + log("SE ports; otherwise the data is used as EN and SE is tied low.\n"); + log("\n"); + } + + static bool is_one_bit_binary(Cell *cell) + { + return cell->getParam(ID::A_WIDTH).as_int() == 1 && + cell->getParam(ID::B_WIDTH).as_int() == 1 && + cell->getParam(ID::Y_WIDTH).as_int() == 1; + } + + static bool is_and(Cell *cell) + { + return cell->type.in(ID($and), ID($logic_and)) && is_one_bit_binary(cell); + } + + static bool is_or(Cell *cell) + { + return cell->type.in(ID($or), ID($logic_or)) && is_one_bit_binary(cell); + } + + static bool is_not(Cell *cell) + { + if (cell->type == ID($_NOT_)) + return true; + return cell->type.in(ID($not), ID($logic_not)) && + cell->getParam(ID::A_WIDTH).as_int() == 1 && + cell->getParam(ID::Y_WIDTH).as_int() == 1; + } + + static void add_driver(dict &drivers, pool &multi_driver_bits, SigMap &sigmap, Cell *cell) + { + for (auto &conn : cell->connections()) { + if (!cell->output(conn.first)) + continue; + for (auto bit : sigmap(conn.second)) { + if (bit.is_wire()) { + if (drivers.count(bit) && drivers.at(bit) != cell) + multi_driver_bits.insert(bit); + else + drivers[bit] = cell; + } + } + } + } + + static bool get_driver(dict &drivers, pool &multi_driver_bits, SigMap &sigmap, SigSpec sig, Cell *&driver) + { + sig = sigmap(sig); + if (GetSize(sig) != 1 || !sig[0].is_wire() || multi_driver_bits.count(sig[0]) || !drivers.count(sig[0])) + return false; + driver = drivers.at(sig[0]); + return true; + } + + static bool same_sig(SigMap &sigmap, SigSpec a, SigSpec b) + { + return sigmap(a) == sigmap(b); + } + + static bool get_not_input(dict &drivers, pool &multi_driver_bits, SigMap &sigmap, + SigSpec sig, SigSpec &input, Cell *¬_cell) + { + Cell *driver = nullptr; + if (!get_driver(drivers, multi_driver_bits, sigmap, sig, driver) || !is_not(driver)) + return false; + + not_cell = driver; + input = sigmap(driver->getPort(ID::A)); + return true; + } + + static bool latch_transparent_when(Cell *latch, SigSpec clk, bool high_when_transparent, + dict &drivers, pool &multi_driver_bits, SigMap &sigmap) + { + SigSpec latch_en = sigmap(latch->getPort(ID::EN)); + bool latch_en_pol = latch->getParam(ID::EN_POLARITY).as_bool(); + + if (same_sig(sigmap, latch_en, clk)) + return latch_en_pol == high_when_transparent; + + SigSpec inv_input; + Cell *not_cell = nullptr; + if (get_not_input(drivers, multi_driver_bits, sigmap, latch_en, inv_input, not_cell) && + same_sig(sigmap, inv_input, clk)) + return latch_en_pol != high_when_transparent; + + return false; + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing INFER_ICG pass (infer $icg cells from latch-based clock gates).\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()) { + SigMap sigmap(module); + dict drivers; + pool multi_driver_bits; + dict users; + pool selected_cells; + + for (auto wire : module->wires()) + if (wire->port_output) + for (auto bit : sigmap(wire)) + if (bit.is_wire()) + users[bit]++; + + for (auto cell : module->cells()) { + if (design->selected(module, cell)) + selected_cells.insert(cell); + + add_driver(drivers, multi_driver_bits, sigmap, cell); + + for (auto &conn : cell->connections()) { + if (!cell->input(conn.first)) + continue; + for (auto bit : sigmap(conn.second)) + if (bit.is_wire()) + users[bit]++; + } + } + + struct Match { + Cell *gate_cell; + Cell *latch; + Cell *enable_or_cell; + SigSpec clk; + SigSpec en; + SigSpec se; + SigSpec gclk; + SigSpec latched_en; + bool invert_clk_and_gclk; + }; + std::vector matches; + pool consumed_gate_cells; + + for (auto gate_cell : module->selected_cells()) { + if (consumed_gate_cells.count(gate_cell) || (!is_and(gate_cell) && !is_or(gate_cell))) + continue; + + bool gate_is_and = is_and(gate_cell); + SigSpec gate_a = sigmap(gate_cell->getPort(ID::A)); + SigSpec gate_b = sigmap(gate_cell->getPort(ID::B)); + SigSpec gclk = gate_cell->getPort(ID::Y); + + for (int clk_port = 0; clk_port < 2; clk_port++) { + SigSpec clk = clk_port == 0 ? gate_a : gate_b; + SigSpec latched_en = clk_port == 0 ? gate_b : gate_a; + Cell *latch = nullptr; + bool invert_clk_and_gclk = false; + + if (!gate_is_and) { + SigSpec not_input; + Cell *not_cell = nullptr; + if (!get_not_input(drivers, multi_driver_bits, sigmap, latched_en, not_input, not_cell)) + continue; + latched_en = not_input; + invert_clk_and_gclk = true; + } + + if (!get_driver(drivers, multi_driver_bits, sigmap, latched_en, latch)) + continue; + if (!selected_cells.count(latch) || latch->type != ID($dlatch)) + continue; + if (latch->getParam(ID::WIDTH).as_int() != 1) + continue; + + if (!latch_transparent_when(latch, clk, !gate_is_and, drivers, multi_driver_bits, sigmap)) + continue; + + SigSpec latch_d = sigmap(latch->getPort(ID::D)); + SigSpec en = latch_d, se = State::S0; + Cell *enable_or_cell = nullptr; + + if (get_driver(drivers, multi_driver_bits, sigmap, latch_d, enable_or_cell) && + selected_cells.count(enable_or_cell) && is_or(enable_or_cell)) { + en = sigmap(enable_or_cell->getPort(ID::A)); + se = sigmap(enable_or_cell->getPort(ID::B)); + } else { + enable_or_cell = nullptr; + } + + matches.push_back({gate_cell, latch, enable_or_cell, clk, en, se, gclk, latched_en, invert_clk_and_gclk}); + consumed_gate_cells.insert(gate_cell); + break; + } + } + + for (auto &match : matches) { + Cell *cell = match.gate_cell; + Cell *icg = module->addCell(NEW_ID2_SUFFIX("icg"), ID($icg)); + SigSpec icg_clk = match.clk; + SigSpec icg_gclk = match.gclk; + + if (match.invert_clk_and_gclk) { + icg_clk = module->Not(NEW_ID2_SUFFIX("icg_clk_n"), match.clk); + icg_gclk = module->addWire(NEW_ID2_SUFFIX("icg_gclk")); + } + + icg->setPort(ID::CLK, icg_clk); + icg->setPort(ID::EN, match.en); + icg->setPort(ID::SE, match.se); + icg->setPort(ID::GCLK, icg_gclk); + icg->add_strpool_attribute(ID::src, match.gate_cell->get_strpool_attribute(ID::src)); + icg->add_strpool_attribute(ID::src, match.latch->get_strpool_attribute(ID::src)); + if (match.enable_or_cell) + icg->add_strpool_attribute(ID::src, match.enable_or_cell->get_strpool_attribute(ID::src)); + + if (match.invert_clk_and_gclk) + module->addNot(NEW_ID2_SUFFIX("icg_gclk_n"), icg_gclk, match.gclk, false, match.gate_cell->get_src_attribute()); + + log("Inferred $icg cell %s.%s from latch %s and gate %s.\n", + log_id(module), log_id(icg), log_id(match.latch), log_id(match.gate_cell)); + + module->remove(match.gate_cell); + + SigSpec latched_en = sigmap(match.latched_en); + if (GetSize(latched_en) == 1 && latched_en[0].is_wire() && users[latched_en[0]] == 1) { + module->remove(match.latch); + + if (match.enable_or_cell) { + SigSpec or_y = sigmap(match.enable_or_cell->getPort(ID::Y)); + if (GetSize(or_y) == 1 && or_y[0].is_wire() && users[or_y[0]] == 1) + module->remove(match.enable_or_cell); + } + } + + total_count++; + } + } + + log("Inferred %d $icg cells.\n", total_count); + } +} InferIcgPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/silimate/ffnormpol.ys b/tests/silimate/ffnormpol.ys new file mode 100644 index 000000000..1afa3b88c --- /dev/null +++ b/tests/silimate/ffnormpol.ys @@ -0,0 +1,116 @@ +log -header "Normalize coarse and fine FF/latch polarities" +log -push +design -reset +read_rtlil <