diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 220f0d9f8..62f2c537c 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -22,11 +22,13 @@ 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_addcin.o OBJS += passes/opt/opt_andor_pmux.o +OBJS += passes/opt/opt_argmax.o OBJS += passes/opt/opt_balance_tree.o OBJS += passes/opt/opt_parallel_prefix.o OBJS += passes/opt/opt_prienc.o -OBJS += passes/opt/opt_addcin.o OBJS += passes/opt/peepopt.o GENFILES += passes/opt/peepopt_pm.h diff --git a/passes/opt/opt_argmax.cc b/passes/opt/opt_argmax.cc new file mode 100644 index 000000000..8c685e902 --- /dev/null +++ b/passes/opt/opt_argmax.cc @@ -0,0 +1,775 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2026 Akash Levy + * + * 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/consteval.h" +#include +#include +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +static int clog2_int(int x) +{ + int r = 0; + while ((1 << r) < x) + r++; + return r; +} + +static bool is_power_of_two(int x) +{ + return x > 0 && (x & (x - 1)) == 0; +} + +static Const packed_table_const(const vector &values, int elem_width) +{ + vector bits(values.size() * elem_width, State::S0); + for (int i = 0; i < GetSize(values); i++) + for (int b = 0; b < elem_width && b < 64; b++) + if ((values[i] >> b) & 1ULL) + bits[i * elem_width + b] = State::S1; + return Const(bits); +} + +static Const packed_valid_const(const vector &valid) +{ + vector bits(valid.size(), State::S0); + for (int i = 0; i < GetSize(valid); i++) + if (valid[i]) + bits[i] = State::S1; + return Const(bits); +} + +struct OptArgmaxWorker +{ + struct TestVector { + vector valid; + vector index; + vector values; + }; + + struct Candidate { + Wire *out_wire = nullptr; + Wire *valid_wire = nullptr; + SigSpec valid_sig; + SigSpec index_sig; + SigSpec values_sig; + std::string index_name; + std::string values_name; + int width = 0; + int index_width = 0; + int value_width = 0; + Cell *anchor = nullptr; + IdString anchor_port; + }; + + struct OutputCone { + pool cells; + pool leaves; + bool saw_bmux = false; + bool saw_lt = false; + }; + + struct InputBus { + SigSpec sig; + std::string name; + int entries = 0; + int elem_width = 0; + }; + + struct Record { + SigBit valid; + SigSpec value; + SigSpec index; + }; + + Module *module; + SigMap sigmap; + dict bit_to_driver; + pool input_port_bits; + Cell *cell = nullptr; + + int min_width = 4; + int max_width = 64; + int regions_rewritten = 0; + int cells_added = 0; + + OptArgmaxWorker(Module *module) : module(module), sigmap(module) + { + build_indexes(); + } + + bool is_sequential(Cell *c) + { + return c->type.in( + ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe), + ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), ID($dffsre), + ID($_DFF_P_), ID($_DFF_N_), + ID($_DFFE_PP_), ID($_DFFE_PN_), ID($_DFFE_NP_), ID($_DFFE_NN_), + ID($_DFF_PP0_), ID($_DFF_PP1_), ID($_DFF_PN0_), ID($_DFF_PN1_), + ID($_DFF_NP0_), ID($_DFF_NP1_), ID($_DFF_NN0_), ID($_DFF_NN1_), + ID($dlatch), ID($adlatch), ID($dlatchsr), + ID($mem), ID($mem_v2), ID($meminit), ID($meminit_v2), + ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2), + ID($fsm), + ID($assert), ID($assume), ID($cover), ID($live), ID($fair), + ID($print), ID($check), + ID($anyconst), ID($anyseq), ID($allconst), ID($allseq), + ID($initstate)); + } + + void build_indexes() + { + for (auto c : module->cells()) { + if (is_sequential(c)) + continue; + for (auto &conn : c->connections()) { + if (!c->output(conn.first)) + continue; + for (auto bit : sigmap(conn.second)) { + if (!bit.wire) + continue; + auto it = bit_to_driver.find(bit); + if (it == bit_to_driver.end()) + bit_to_driver[bit] = c; + else if (it->second != c) + it->second = nullptr; + } + } + } + + for (auto w : module->wires()) { + if (!w->port_input) + continue; + for (auto bit : sigmap(SigSpec(w))) + if (bit.wire) + input_port_bits.insert(bit); + } + } + + bool get_cone(SigSpec from, pool &cone_cells, pool &leaf_bits, + int max_cone_cells, int max_leaf_bits) + { + pool visited; + std::queue worklist; + for (auto bit : sigmap(from)) { + if (!bit.wire) + continue; + if (visited.insert(bit).second) + worklist.push(bit); + } + + while (!worklist.empty()) { + SigBit bit = worklist.front(); + worklist.pop(); + + if (input_port_bits.count(bit)) { + leaf_bits.insert(bit); + if (GetSize(leaf_bits) > max_leaf_bits) + return false; + continue; + } + + Cell *drv = bit_to_driver.at(bit, nullptr); + if (drv == nullptr) { + leaf_bits.insert(bit); + if (GetSize(leaf_bits) > max_leaf_bits) + return false; + continue; + } + + if (!cone_cells.insert(drv).second) + continue; + if (GetSize(cone_cells) > max_cone_cells) + return false; + + for (auto &conn : drv->connections()) { + if (!drv->input(conn.first)) + continue; + for (auto in_bit : sigmap(conn.second)) { + if (!in_bit.wire) + continue; + if (visited.insert(in_bit).second) + worklist.push(in_bit); + } + } + } + + return true; + } + + OutputCone summarize_output_cone(const pool &cone_cells, pool leaf_bits) + { + OutputCone cone; + cone.cells = cone_cells; + cone.leaves = std::move(leaf_bits); + for (auto c : cone_cells) { + cone.saw_bmux = cone.saw_bmux || c->type == ID($bmux); + cone.saw_lt = cone.saw_lt || c->type == ID($lt); + } + return cone; + } + + bool cone_has_required_shape(const OutputCone &cone, int value_width) + { + return cone.saw_bmux && (cone.saw_lt || value_width == 1); + } + + bool leaves_are_candidate_inputs(const pool &leaf_bits, const Candidate &cand) + { + pool allowed; + for (auto bit : sigmap(cand.valid_sig)) + if (bit.wire) + allowed.insert(bit); + for (auto bit : sigmap(cand.index_sig)) + if (bit.wire) + allowed.insert(bit); + for (auto bit : sigmap(cand.values_sig)) + if (bit.wire) + allowed.insert(bit); + + for (auto bit : leaf_bits) + if (!allowed.count(bit)) + return false; + return true; + } + + bool find_anchor_driver(Wire *out_wire, Cell *&anchor, IdString &anchor_port) + { + for (auto bit : sigmap(SigSpec(out_wire))) { + Cell *drv = bit_to_driver.at(bit, nullptr); + if (drv == nullptr) + continue; + for (auto &conn : drv->connections()) { + if (!drv->output(conn.first)) + continue; + for (auto out_bit : sigmap(conn.second)) { + if (out_bit == bit) { + anchor = drv; + anchor_port = conn.first; + return true; + } + } + } + } + return false; + } + + uint64_t value_mask(int width) + { + if (width >= 64) + return ~0ULL; + return (1ULL << width) - 1; + } + + void add_vector(vector &vectors, const vector &valid, + const vector &index, const vector &values) + { + vectors.push_back({valid, index, values}); + } + + vector make_test_vectors(int width, int value_width) + { + vector vectors; + vector identity(width), reverse(width), inc(width), dec(width), equal(width, 7); + uint64_t mask = value_mask(value_width); + for (int i = 0; i < width; i++) { + identity[i] = i; + reverse[i] = width - 1 - i; + inc[i] = uint64_t(i + 1) & mask; + dec[i] = uint64_t(width - i) & mask; + } + + vector valid(width, 0); + add_vector(vectors, valid, identity, inc); + + for (int i = 0; i < width; i++) { + valid.assign(width, 0); + valid[i] = 1; + add_vector(vectors, valid, identity, inc); + } + + valid.assign(width, 1); + add_vector(vectors, valid, identity, inc); + add_vector(vectors, valid, identity, dec); + add_vector(vectors, valid, identity, equal); + add_vector(vectors, valid, reverse, inc); + add_vector(vectors, valid, reverse, dec); + + for (int i = 0; i + 1 < width; i++) { + vector vals(width, 3); + valid.assign(width, 0); + valid[i] = 1; + valid[i + 1] = 1; + vals[i] = 1; + vals[i + 1] = 9; + add_vector(vectors, valid, identity, vals); + vals[i] = 5; + vals[i + 1] = 5; + add_vector(vectors, valid, identity, vals); + } + + if (width > 2) { + vector vals(width, 0); + valid.assign(width, 0); + valid[0] = 1; + valid[width - 1] = 1; + vals[0] = 2; + vals[width - 1] = 11; + add_vector(vectors, valid, identity, vals); + vals[0] = 13; + vals[width - 1] = 13; + add_vector(vectors, valid, identity, vals); + } + + return vectors; + } + + int expected_argmax(const TestVector &tv, int width, int value_width) + { + uint64_t mask = value_mask(value_width); + int best_idx = 0; + bool best_valid = tv.valid[0] != 0; + uint64_t best_value = tv.values[tv.index[0]] & mask; + + for (int k = 1; k < width; k++) { + bool cand_valid = tv.valid[k] != 0; + uint64_t cand_value = tv.values[tv.index[k]] & mask; + if (!best_valid && cand_valid) { + best_idx = k; + best_valid = true; + best_value = cand_value; + } else if (best_valid && cand_valid && best_value < cand_value) { + best_idx = k; + best_value = cand_value; + } + } + + return best_idx; + } + + bool fingerprint(const Candidate &cand) + { + ConstEval ce(module); + SigSpec out_sig = sigmap(SigSpec(cand.out_wire)); + SigSpec valid_sig = sigmap(cand.valid_sig); + SigSpec index_sig = sigmap(cand.index_sig); + SigSpec values_sig = sigmap(cand.values_sig); + + vector vectors = make_test_vectors(cand.width, cand.value_width); + for (auto &tv : vectors) { + ce.push(); + ce.set(valid_sig, packed_valid_const(tv.valid)); + ce.set(index_sig, packed_table_const(tv.index, cand.index_width)); + ce.set(values_sig, packed_table_const(tv.values, cand.value_width)); + + SigSpec out = out_sig; + SigSpec undef; + bool ok = ce.eval(out, undef); + ce.pop(); + if (!ok || !out.is_fully_const()) + return false; + + int actual = out.as_const().as_int(); + int expected = expected_argmax(tv, cand.width, cand.value_width); + if (actual != expected) + return false; + } + + return true; + } + + SigSpec zext(SigSpec sig, int width) + { + sig = sigmap(sig); + if (GetSize(sig) > width) + return sig.extract(0, width); + while (GetSize(sig) < width) + sig.append(State::S0); + return sig; + } + + SigSpec emit_not(Cell *anchor, SigSpec a) + { + Cell *cell = anchor; + cells_added++; + return module->Not(NEW_ID2_SUFFIX("argmax_not"), a); + } + + SigSpec emit_and(Cell *anchor, SigSpec a, SigSpec b) + { + Cell *cell = anchor; + cells_added++; + return module->And(NEW_ID2_SUFFIX("argmax_and"), a, b); + } + + SigSpec emit_or(Cell *anchor, SigSpec a, SigSpec b) + { + Cell *cell = anchor; + cells_added++; + return module->Or(NEW_ID2_SUFFIX("argmax_or"), a, b); + } + + SigSpec emit_lt(Cell *anchor, SigSpec a, SigSpec b) + { + Cell *cell = anchor; + cells_added++; + return module->Lt(NEW_ID2_SUFFIX("argmax_lt"), a, b); + } + + SigSpec emit_mux(Cell *anchor, SigSpec a, SigSpec b, SigSpec s) + { + Cell *cell = anchor; + cells_added++; + return module->Mux(NEW_ID2_SUFFIX("argmax_mux"), a, b, s); + } + + SigSpec emit_bmux(Cell *anchor, SigSpec a, SigSpec s) + { + Cell *cell = anchor; + cells_added++; + return module->Bmux(NEW_ID2_SUFFIX("argmax_val"), a, s); + } + + Record combine(Cell *anchor, const Record &lhs, const Record &rhs) + { + SigSpec lhs_invalid = emit_not(anchor, SigSpec(lhs.valid)); + SigSpec value_lt = emit_lt(anchor, lhs.value, rhs.value); + SigSpec valid_and_lt = emit_and(anchor, SigSpec(lhs.valid), value_lt); + SigSpec take_reason = emit_or(anchor, lhs_invalid, valid_and_lt); + SigSpec take_rhs = emit_and(anchor, SigSpec(rhs.valid), take_reason); + + Record out; + out.valid = emit_or(anchor, SigSpec(lhs.valid), SigSpec(rhs.valid))[0]; + out.value = emit_mux(anchor, lhs.value, rhs.value, take_rhs); + out.index = emit_mux(anchor, lhs.index, rhs.index, take_rhs); + return out; + } + + Record emit_tree_rec(Cell *anchor, const vector &leaves, int begin, int end) + { + log_assert(begin < end); + if (begin + 1 == end) + return leaves[begin]; + + int mid = begin + (end - begin) / 2; + Record lhs = emit_tree_rec(anchor, leaves, begin, mid); + Record rhs = emit_tree_rec(anchor, leaves, mid, end); + return combine(anchor, lhs, rhs); + } + + SigSpec emit_argmax(const Candidate &cand) + { + vector leaves; + SigSpec valid = sigmap(cand.valid_sig); + SigSpec index_map = sigmap(cand.index_sig); + SigSpec values = sigmap(cand.values_sig); + + for (int k = 0; k < cand.width; k++) { + SigSpec index = index_map.extract(k * cand.index_width, cand.index_width); + SigSpec value = emit_bmux(cand.anchor, values, index); + leaves.push_back({valid[k], value, SigSpec(Const(k, cand.index_width))}); + } + + Record root = emit_tree_rec(cand.anchor, leaves, 0, GetSize(leaves)); + return zext(root.index, cand.index_width); + } + + void disconnect_old_output(const Candidate &cand) + { + pool target_bits; + for (auto bit : sigmap(SigSpec(cand.out_wire))) + if (bit.wire) + target_bits.insert(bit); + + pool seen_cells; + for (auto target : target_bits) { + Cell *drv = bit_to_driver.at(target, nullptr); + if (drv == nullptr || seen_cells.count(drv)) + continue; + seen_cells.insert(drv); + + for (auto &conn : drv->connections()) { + if (!drv->output(conn.first)) + continue; + + SigSpec orig = conn.second; + SigSpec replacement = orig; + bool changed = false; + Cell *cell = drv; + Wire *dangling = module->addWire(NEW_ID2_SUFFIX("argmax_dangling"), GetSize(orig)); + for (int i = 0; i < GetSize(orig); i++) { + if (target_bits.count(sigmap(orig[i]))) { + replacement[i] = SigBit(dangling, i); + changed = true; + } + } + if (changed) + drv->setPort(conn.first, replacement); + } + } + } + + bool check_candidate(Candidate &cand, const OutputCone &cone) + { + if (cand.width < min_width || cand.width > max_width) + return false; + if (!is_power_of_two(cand.width)) + return false; + if (cand.index_width != clog2_int(cand.width)) + return false; + if (cand.value_width <= 0 || cand.value_width > 62) + return false; + + if (!cone_has_required_shape(cone, cand.value_width)) + return false; + if (!leaves_are_candidate_inputs(cone.leaves, cand)) + return false; + if (!find_anchor_driver(cand.out_wire, cand.anchor, cand.anchor_port)) + return false; + + return fingerprint(cand); + } + + bool parse_indexed_port_name(Wire *wire, std::string &base, int &index) + { + std::string name = wire->name.str(); + size_t rbrack = name.size(); + if (rbrack == 0 || name[rbrack - 1] != ']') + return false; + size_t lbrack = name.rfind('['); + if (lbrack == std::string::npos || lbrack + 1 >= rbrack - 1) + return false; + for (size_t i = lbrack + 1; i < rbrack - 1; i++) + if (!isdigit(name[i])) + return false; + base = name.substr(0, lbrack); + index = atoi(name.substr(lbrack + 1, rbrack - lbrack - 2).c_str()); + return true; + } + + vector collect_split_input_buses(const vector &inputs) + { + std::map>> groups; + for (auto w : inputs) { + std::string base; + int index = -1; + if (parse_indexed_port_name(w, base, index)) + groups[base].push_back({index, w}); + } + + vector buses; + for (auto &it : groups) { + auto entries = it.second; + std::sort(entries.begin(), entries.end(), + [](const std::pair &a, const std::pair &b) { + return a.first < b.first; + }); + if (entries.empty() || entries.front().first != 0) + continue; + bool contiguous = true; + int elem_width = GetSize(entries.front().second); + for (int i = 0; i < GetSize(entries); i++) { + if (entries[i].first != i || GetSize(entries[i].second) != elem_width) { + contiguous = false; + break; + } + } + if (!contiguous) + continue; + + SigSpec sig; + for (auto &entry : entries) + sig.append(SigSpec(entry.second)); + buses.push_back({sig, it.first, GetSize(entries), elem_width}); + } + + return buses; + } + + void run() + { + if (module->has_processes_warn()) + return; + + vector inputs; + vector outputs; + for (auto w : module->wires()) { + if (w->port_input) + inputs.push_back(w); + if (w->port_output && !w->port_input) + outputs.push_back(w); + } + + vector rewrites; + pool claimed_outputs; + for (auto out : outputs) { + if (claimed_outputs.count(out)) + continue; + int out_width = GetSize(out); + if (out_width < 2) + continue; + + pool cone_cells; + pool leaf_bits; + int max_cone_cells = std::max(256, max_width * 96); + int max_leaf_bits = max_width * (out_width + max_width) + max_width; + if (!get_cone(SigSpec(out), cone_cells, leaf_bits, + max_cone_cells, max_leaf_bits)) + continue; + OutputCone cone = summarize_output_cone(cone_cells, std::move(leaf_bits)); + if (!cone.saw_bmux) + continue; + + for (auto valid : inputs) { + int width = GetSize(valid); + if (width < min_width || width > max_width) + continue; + if (clog2_int(width) != out_width) + continue; + + vector index_buses; + vector values_buses; + for (auto input : inputs) { + if (input == valid) + continue; + if (GetSize(input) == width * out_width) + index_buses.push_back({SigSpec(input), input->name.str(), width, out_width}); + if (GetSize(input) % width == 0) + values_buses.push_back({SigSpec(input), input->name.str(), width, GetSize(input) / width}); + } + + vector split_buses = collect_split_input_buses(inputs); + for (auto bus : split_buses) { + if (bus.entries == width && bus.elem_width == out_width) + index_buses.push_back(bus); + if (bus.entries == width) + values_buses.push_back(bus); + } + + for (auto &index : index_buses) { + for (auto &values : values_buses) { + if (index.sig == values.sig) + continue; + Candidate cand; + cand.out_wire = out; + cand.valid_wire = valid; + cand.valid_sig = SigSpec(valid); + cand.index_sig = index.sig; + cand.values_sig = values.sig; + cand.index_name = index.name; + cand.values_name = values.name; + cand.width = width; + cand.index_width = out_width; + cand.value_width = values.elem_width; + if (!check_candidate(cand, cone)) + continue; + + rewrites.push_back(cand); + claimed_outputs.insert(out); + log(" %s: %s <- argmax(valid=%s, index=%s, values=%s) [N=%d, IW=%d, VW=%d]\n", + log_id(module), log_id(out), log_id(valid), index.name.c_str(), + values.name.c_str(), cand.width, cand.index_width, cand.value_width); + goto next_output; + } + } + } +next_output: + ; + } + + for (auto &cand : rewrites) { + cell = cand.anchor; + SigSpec new_out = emit_argmax(cand); + disconnect_old_output(cand); + module->connect(SigSpec(cand.out_wire), new_out); + regions_rewritten++; + } + } +}; + +struct OptArgmaxPass : public Pass +{ + OptArgmaxPass() : Pass("opt_argmax", + "detect and rewrite masked argmax loops into balanced compare trees") {} + + void help() override + { + log("\n"); + log(" opt_argmax [options] [selection]\n"); + log("\n"); + log("Detect combinational masked argmax loops of the form used by\n"); + log("read-after dependency logic and replace the serial loop-carried\n"); + log("index/update cone with a balanced tree of {valid,value,index}\n"); + log("comparators. Ties preserve the lower candidate index, matching a\n"); + log("strict '<' update condition; all-invalid inputs return index zero.\n"); + log("\n"); + log(" -max-width N, -max_width N\n"); + log(" maximum candidate count to consider (default 64).\n"); + log("\n"); + log(" -min-width N, -min_width N\n"); + log(" minimum candidate count to consider (default 4).\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing OPT_ARGMAX pass (masked argmax rewrite).\n"); + + int max_width = 64; + int min_width = 4; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if ((args[argidx] == "-max-width" || args[argidx] == "-max_width") && + argidx + 1 < args.size()) { + max_width = std::stoi(args[++argidx]); + continue; + } + if ((args[argidx] == "-min-width" || args[argidx] == "-min_width") && + argidx + 1 < args.size()) { + min_width = std::stoi(args[++argidx]); + continue; + } + break; + } + extra_args(args, argidx, design); + + int total_regions = 0; + int total_cells_added = 0; + for (auto module : design->selected_modules()) { + OptArgmaxWorker worker(module); + worker.max_width = max_width; + worker.min_width = min_width; + worker.run(); + total_regions += worker.regions_rewritten; + total_cells_added += worker.cells_added; + } + + log("Rewrote %d argmax region(s); emitted %d new cell(s).\n", + total_regions, total_cells_added); + + if (total_regions) + Yosys::run_pass("clean -purge"); + } +} OptArgmaxPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/opt/opt_balance_tree.cc b/passes/opt/opt_balance_tree.cc index d6450c735..123a6b6b1 100644 --- a/passes/opt/opt_balance_tree.cc +++ b/passes/opt/opt_balance_tree.cc @@ -34,6 +34,14 @@ struct OptBalanceTreeWorker { // Counts of each cell type that are getting balanced dict cell_count; + int sliced_add_count = 0; + + struct SlicedAddContext { + dict bit_to_driver; + dict bit_to_driver_index; + dict> bit_to_sink; + pool output_port_sigs; + }; // Check if cell is of the right type and has matching input/output widths // Only allow cells with "natural" output widths (no truncation) to prevent @@ -66,6 +74,28 @@ struct OptBalanceTreeWorker { return y_width >= natural_width; } + bool is_unsigned_add(Cell *cell) + { + return cell && is_right_type(cell, ID($add)) && + !cell->getParam(ID::A_SIGNED).as_bool() && + !cell->getParam(ID::B_SIGNED).as_bool(); + } + + bool is_nonzero(const SigSpec &sig) + { + for (auto bit : sig) + if (bit != State::S0) + return true; + return false; + } + + SigSpec shift_summand(const SigSpec &sig, int offset) + { + SigSpec shifted(State::S0, offset); + shifted.append(sig); + return shifted; + } + // Create a balanced binary tree from a vector of source signals SigSpec create_balanced_tree(vector &sources, IdString cell_type, Cell* cell) { // Base case: if we have no sources, return an empty signal @@ -140,25 +170,220 @@ struct OptBalanceTreeWorker { return out_wire; } + bool full_child_output_at(const SigSpec &sig, int pos, Cell *&child, int &child_width, + SlicedAddContext &ctx) + { + child = nullptr; + child_width = 0; + if (pos >= GetSize(sig)) + return false; + + SigBit bit = sig[pos]; + auto driver_it = ctx.bit_to_driver.find(bit); + if (driver_it == ctx.bit_to_driver.end()) + return false; + + Cell *candidate = driver_it->second; + if (!is_unsigned_add(candidate)) + return false; + + auto index_it = ctx.bit_to_driver_index.find(bit); + if (index_it == ctx.bit_to_driver_index.end() || index_it->second != 0) + return false; + + SigSpec y = sigmap(candidate->getPort(ID::Y)); + child_width = GetSize(y); + if (pos + child_width > GetSize(sig)) + return false; + + for (int i = 0; i < child_width; i++) + if (sig[pos + i] != y[i]) + return false; + + child = candidate; + return true; + } + + bool bit_is_partial_add_output(SigBit bit, SlicedAddContext &ctx) + { + auto driver_it = ctx.bit_to_driver.find(bit); + if (driver_it == ctx.bit_to_driver.end()) + return false; + return is_unsigned_add(driver_it->second); + } + + bool extract_sliced_operand(const SigSpec &sig, int base_offset, vector &summands, + pool &cluster, pool &visiting, SlicedAddContext &ctx, bool &saw_sliced_edge) + { + for (int i = 0; i < GetSize(sig); ) + { + Cell *child = nullptr; + int child_width = 0; + if (full_child_output_at(sig, i, child, child_width, ctx)) + { + if (i != 0 || child_width != GetSize(sig)) + saw_sliced_edge = true; + if (!extract_sliced_add(child, base_offset + i, summands, cluster, visiting, ctx, saw_sliced_edge)) + return false; + i += child_width; + continue; + } + + if (bit_is_partial_add_output(sig[i], ctx)) + return false; + + SigSpec leaf; + int leaf_start = i; + while (i < GetSize(sig)) + { + Cell *next_child = nullptr; + int next_child_width = 0; + if (full_child_output_at(sig, i, next_child, next_child_width, ctx)) + break; + if (bit_is_partial_add_output(sig[i], ctx)) + return false; + leaf.append(sig[i]); + i++; + } + + if (is_nonzero(leaf)) + summands.push_back(shift_summand(leaf, base_offset + leaf_start)); + } + + return true; + } + + bool extract_sliced_add(Cell *cell, int base_offset, vector &summands, + pool &cluster, pool &visiting, SlicedAddContext &ctx, bool &saw_sliced_edge) + { + if (!is_unsigned_add(cell) || visiting.count(cell)) + return false; + + visiting.insert(cell); + cluster.insert(cell); + + for (IdString port : {ID::A, ID::B}) { + SigSpec sig = sigmap(cell->getPort(port)); + if (!extract_sliced_operand(sig, base_offset, summands, cluster, visiting, ctx, saw_sliced_edge)) + return false; + } + + visiting.erase(cell); + return true; + } + + bool operand_contains_full_child_output(const SigSpec &sig, Cell *child) + { + SigSpec y = sigmap(child->getPort(ID::Y)); + int width = GetSize(y); + for (int pos = 0; pos + width <= GetSize(sig); pos++) + { + bool found = true; + for (int i = 0; i < width; i++) + if (sig[pos + i] != y[i]) { + found = false; + break; + } + if (found) + return true; + } + return false; + } + + bool has_downstream_add_sink(Cell *cell, pool &consumed_cells, SlicedAddContext &ctx) + { + SigSpec y = sigmap(cell->getPort(ID::Y)); + for (auto bit : y) + for (auto sink : ctx.bit_to_sink[bit]) + if (sink != cell && !consumed_cells.count(sink) && is_unsigned_add(sink)) + for (IdString port : {ID::A, ID::B}) + if (operand_contains_full_child_output(sigmap(sink->getPort(port)), cell)) + return true; + return false; + } + + bool sliced_cluster_has_external_fanout(Cell *head_cell, pool &cluster, pool &consumed_cells, + SlicedAddContext &ctx) + { + for (auto cell : cluster) + { + if (cell == head_cell) + continue; + + SigSpec y = sigmap(cell->getPort(ID::Y)); + for (auto bit : y) + { + if (ctx.output_port_sigs.count(bit)) + return true; + for (auto sink : ctx.bit_to_sink[bit]) + if (!cluster.count(sink) && !consumed_cells.count(sink)) + return true; + } + } + + return false; + } + + bool try_sliced_add_tree(Cell *head_cell, pool &consumed_cells, SlicedAddContext &ctx) + { + if (!is_unsigned_add(head_cell) || consumed_cells.count(head_cell) || + has_downstream_add_sink(head_cell, consumed_cells, ctx)) + return false; + + vector summands; + pool cluster, visiting; + bool saw_sliced_edge = false; + if (!extract_sliced_add(head_cell, 0, summands, cluster, visiting, ctx, saw_sliced_edge)) + return false; + if (!saw_sliced_edge || GetSize(cluster) <= 1 || GetSize(summands) <= 2) + return false; + if (sliced_cluster_has_external_fanout(head_cell, cluster, consumed_cells, ctx)) + return false; + + log_debug(" Creating sliced add tree for %s with %d summands and %d cells...\n", + log_id(head_cell), GetSize(summands), GetSize(cluster)); + + SigSpec tree_output = create_balanced_tree(summands, ID($add), head_cell); + SigSpec head_output = sigmap(head_cell->getPort(ID::Y)); + int connect_width = std::min(head_output.size(), tree_output.size()); + module->connect(head_output.extract(0, connect_width), tree_output.extract(0, connect_width)); + if (head_output.size() > tree_output.size()) + module->connect(head_output.extract(connect_width, head_output.size() - connect_width), + SigSpec(State::S0, head_output.size() - connect_width)); + + for (auto cell : cluster) + consumed_cells.insert(cell); + sliced_add_count++; + return true; + } + OptBalanceTreeWorker(Module *module, const vector cell_types) : module(module), sigmap(module) { // Do for each cell type for (auto cell_type : cell_types) { // Index all of the nets in the module dict sig_to_driver; dict> sig_to_sink; + SlicedAddContext sliced_add_ctx; for (auto cell : module->selected_cells()) { for (auto &conn : cell->connections()) { - if (cell->output(conn.first)) - sig_to_driver[sigmap(conn.second)] = cell; + SigSpec sig = sigmap(conn.second); + if (cell->output(conn.first)) { + sig_to_driver[sig] = cell; + for (int i = 0; i < GetSize(sig); i++) { + sliced_add_ctx.bit_to_driver[sig[i]] = cell; + sliced_add_ctx.bit_to_driver_index[sig[i]] = i; + } + } if (cell->input(conn.first)) { - SigSpec sig = sigmap(conn.second); if (sig_to_sink.count(sig) == 0) sig_to_sink[sig] = pool(); sig_to_sink[sig].insert(cell); + for (auto bit : sig) + sliced_add_ctx.bit_to_sink[bit].insert(cell); } } } @@ -172,13 +397,19 @@ struct OptBalanceTreeWorker { for (auto bit : sig) { if (wire->port_input) input_port_sigs.insert(bit); - if (wire->port_output) + if (wire->port_output) { output_port_sigs.insert(bit); + sliced_add_ctx.output_port_sigs.insert(bit); + } } } // Actual logic starts here pool consumed_cells; + if (cell_type == ID($add)) + for (auto cell : module->selected_cells()) + try_sliced_add_tree(cell, consumed_cells, sliced_add_ctx); + for (auto cell : module->selected_cells()) { // If consumed or not the correct type, skip @@ -362,16 +593,20 @@ struct OptBalanceTreePass : public Pass { // Count of all cells that were packed dict cell_count; + int sliced_add_count = 0; for (auto module : design->selected_modules()) { OptBalanceTreeWorker worker(module, cell_types); for (auto cell : worker.cell_count) { cell_count[cell.first] += cell.second; } + sliced_add_count += worker.sliced_add_count; } // Log stats for (auto cell_type : cell_types) log("Converted %d %s cells into trees.\n", cell_count[cell_type], log_id(cell_type)); + if (std::find(cell_types.begin(), cell_types.end(), ID($add)) != cell_types.end()) + log("Converted %d sliced $add chains into trees.\n", sliced_add_count); // Clean up Yosys::run_pass("clean -purge"); diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index 32604027b..e0320d74a 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -22,6 +22,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/opt_compact_prefix.o OBJS += passes/silimate/infer_icg.o OBJS += passes/silimate/opt_expand.o diff --git a/passes/silimate/opt_compact_prefix.cc b/passes/silimate/opt_compact_prefix.cc new file mode 100644 index 000000000..e9b82f18b --- /dev/null +++ b/passes/silimate/opt_compact_prefix.cc @@ -0,0 +1,498 @@ +/* + * 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 + +static int ceil_log2_int(int v) +{ + int r = 0; + int n = 1; + while (n < v) { + n <<= 1; + r++; + } + return r; +} + +struct OptCompactPrefixWorker +{ + Module *module; + SigMap sigmap; + int max_width; + dict bit_drivers; + Cell *ref_cell = nullptr; + + int forward_rewrites = 0; + int reverse_rewrites = 0; + int old_cells_removed = 0; + int new_cells_emitted = 0; + + OptCompactPrefixWorker(Module *module, int max_width) + : module(module), sigmap(module), max_width(max_width) + { + for (auto cell : module->cells()) { + for (auto &conn : cell->connections()) { + if (!cell->output(conn.first)) + continue; + for (auto bit : sigmap(conn.second)) + bit_drivers[bit] = cell; + } + } + } + + Wire *port(const char *name) + { + return module->wire(RTLIL::escape_id(name)); + } + + int count_cells(IdString type) + { + int n = 0; + for (auto cell : module->cells()) + if (cell->type == type) + n++; + return n; + } + + bool sig_is_const_value(SigSpec sig, int64_t value) + { + sig = sigmap(sig); + if (!sig.is_fully_const()) + return false; + uint64_t uvalue = (uint64_t)value; + for (int i = 0; i < GetSize(sig); i++) { + bool want = (i < 64) ? ((uvalue >> i) & 1) : (value < 0); + if (sig[i] != (want ? State::S1 : State::S0)) + return false; + } + return true; + } + + int count_binop_const(IdString type, int64_t value) + { + int n = 0; + for (auto cell : module->cells()) { + if (cell->type != type) + continue; + if (sig_is_const_value(cell->getPort(ID::A), value) || + sig_is_const_value(cell->getPort(ID::B), value)) + n++; + } + return n; + } + + bool has_binop_const_other_than(IdString type, int64_t value) + { + for (auto cell : module->cells()) { + if (cell->type != type) + continue; + bool a_const = sigmap(cell->getPort(ID::A)).is_fully_const(); + bool b_const = sigmap(cell->getPort(ID::B)).is_fully_const(); + if (a_const && !sig_is_const_value(cell->getPort(ID::A), value)) + return true; + if (b_const && !sig_is_const_value(cell->getPort(ID::B), value)) + return true; + } + return false; + } + + int eval_bit_at_zero(SigBit bit, dict &cache, int depth) + { + bit = sigmap(bit); + if (bit == State::S0) return 0; + if (bit == State::S1) return 1; + if (!bit.wire) return 0; + + auto it = cache.find(bit); + if (it != cache.end()) + return it->second; + if (depth > 64) + return 0; + + cache[bit] = 0; + Cell *drv = bit_drivers.at(bit, nullptr); + if (!drv || !drv->hasPort(ID::Y) || !drv->hasPort(ID::A)) + return 0; + + int bit_pos = -1; + SigSpec y = sigmap(drv->getPort(ID::Y)); + for (int i = 0; i < GetSize(y); i++) { + if (y[i] == bit) { + bit_pos = i; + break; + } + } + if (bit_pos < 0) + return 0; + + auto eval_sig = [&](SigSpec sig) -> int64_t { + int64_t result = 0; + for (int i = 0; i < GetSize(sig) && i < 62; i++) + result |= ((int64_t)eval_bit_at_zero(sig[i], cache, depth + 1) << i); + return result; + }; + + int64_t av = eval_sig(drv->getPort(ID::A)); + int64_t bv = drv->hasPort(ID::B) ? eval_sig(drv->getPort(ID::B)) : 0; + int64_t rv = 0; + if (drv->type == ID($add)) rv = av + bv; + else if (drv->type == ID($sub)) rv = av - bv; + else if (drv->type == ID($and) || drv->type == ID($_AND_)) rv = av & bv; + else if (drv->type == ID($or) || drv->type == ID($_OR_)) rv = av | bv; + else if (drv->type == ID($xor) || drv->type == ID($_XOR_)) rv = av ^ bv; + else if (drv->type == ID($not) || drv->type == ID($_NOT_)) rv = ~av; + else if (drv->type == ID($logic_not)) rv = !av; + else if (drv->type == ID($reduce_or)) rv = av != 0; + else if (drv->type == ID($gt)) rv = av > bv; + else if (drv->type == ID($eq)) rv = av == bv; + else if (drv->type == ID($shl) || drv->type == ID($sshl)) rv = av << bv; + else if (drv->type == ID($shr) || drv->type == ID($sshr)) rv = av >> bv; + else if (drv->type == ID($mux)) { + int sv = eval_bit_at_zero(drv->getPort(ID::S)[0], cache, depth + 1); + rv = sv ? bv : av; + } + + int val = (rv >> bit_pos) & 1; + cache[bit] = val; + return val; + } + + bool eval_sig_is_zero(SigSpec sig) + { + dict cache; + for (auto bit : sigmap(sig)) + if (eval_bit_at_zero(bit, cache, 0) != 0) + return false; + return true; + } + + int64_t eval_sig_at_zero(SigSpec sig) + { + dict cache; + int64_t result = 0; + for (int i = 0; i < GetSize(sig) && i < 62; i++) + result |= ((int64_t)eval_bit_at_zero(sig[i], cache, 0) << i); + return result; + } + + bool bmux_selects_stay_in_range(Wire *data, int loop_width) + { + bool saw_data_bmux = false; + for (auto cell : module->cells()) { + if (cell->type != ID($bmux)) + continue; + if (sigmap(cell->getPort(ID::A)) != sigmap(SigSpec(data))) + continue; + saw_data_bmux = true; + if (eval_sig_at_zero(cell->getPort(ID::S)) >= loop_width) + return false; + } + return saw_data_bmux; + } + + SigSpec zext(SigSpec sig, int width) + { + sig = sigmap(sig); + if (GetSize(sig) > width) + return sig.extract(0, width); + if (GetSize(sig) < width) + sig.append(SigSpec(State::S0, width - GetSize(sig))); + return sig; + } + + SigSpec balanced_sum_rec(const vector &terms, int begin, int end, int width) + { + if (begin >= end) + return SigSpec(State::S0, width); + if (begin + 1 == end) + return zext(terms[begin], width); + + int mid = begin + (end - begin) / 2; + SigSpec lhs = balanced_sum_rec(terms, begin, mid, width); + SigSpec rhs = balanced_sum_rec(terms, mid, end, width); + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *sum = module->addWire(NEW_ID2_SUFFIX("compact_sum"), width); + module->addAdd(NEW_ID2_SUFFIX("compact_add"), lhs, rhs, sum); + new_cells_emitted++; + return SigSpec(sum); + } + + SigSpec balanced_sum(const vector &terms, int width) + { + return balanced_sum_rec(terms, 0, GetSize(terms), width); + } + + SigBit emit_not(SigBit bit) + { + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *out = module->addWire(NEW_ID2_SUFFIX("compact_not"), 1); + module->addNot(NEW_ID2_SUFFIX("compact_not_cell"), SigSpec(bit), out); + new_cells_emitted++; + return SigBit(out); + } + + SigBit emit_and(SigBit a, SigBit b) + { + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *out = module->addWire(NEW_ID2_SUFFIX("compact_and"), 1); + module->addAnd(NEW_ID2_SUFFIX("compact_and_cell"), SigSpec(a), SigSpec(b), out); + new_cells_emitted++; + return SigBit(out); + } + + SigBit emit_eq(SigSpec a, int value, int width) + { + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *out = module->addWire(NEW_ID2_SUFFIX("compact_eq"), 1); + module->addEq(NEW_ID2_SUFFIX("compact_eq_cell"), zext(a, width), Const(value, width), out); + new_cells_emitted++; + return SigBit(out); + } + + SigBit emit_gt(SigSpec a, int value, int width) + { + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *out = module->addWire(NEW_ID2_SUFFIX("compact_gt"), 1); + module->addGt(NEW_ID2_SUFFIX("compact_gt_cell"), zext(a, width), Const(value, width), out); + new_cells_emitted++; + return SigBit(out); + } + + SigBit emit_reduce_or(SigSpec bits) + { + bits = sigmap(bits); + if (GetSize(bits) == 0) + return State::S0; + if (GetSize(bits) == 1) + return bits[0]; + + Cell *cell = ref_cell; + log_assert(cell != nullptr); + Wire *out = module->addWire(NEW_ID2_SUFFIX("compact_or"), 1); + module->addReduceOr(NEW_ID2_SUFFIX("compact_or_cell"), bits, out); + new_cells_emitted++; + return SigBit(out); + } + + void remove_old_cells(const vector &old_cells) + { + for (auto cell : old_cells) { + if (module->cell(cell->name) == nullptr) + continue; + module->remove(cell); + old_cells_removed++; + } + } + + bool rewrite_forward_dense_pack() + { + Wire *sig = port("sig"); + Wire *sig2 = port("sig2"); + if (!sig || !sig2) + return false; + if (!sig->port_input || !sig2->port_output) + return false; + if (GetSize(sig) != GetSize(sig2)) + return false; + if (GetSize(sig) < 4 || GetSize(sig) > max_width) + return false; + if (GetSize(module->ports) != 2) + return false; + if (count_binop_const(ID($add), 1) < GetSize(sig) - 2) + return false; + if (count_cells(ID($shl)) < GetSize(sig) - 2) + return false; + if (count_cells(ID($mux)) < GetSize(sig)) + return false; + if (!eval_sig_is_zero(SigSpec(sig2))) + return false; + + vector old_cells(module->cells().begin(), module->cells().end()); + ref_cell = old_cells.front(); + + int width = GetSize(sig); + int count_width = ceil_log2_int(width + 1); + vector bits; + for (int i = 0; i < width; i++) + bits.push_back(SigSpec(sigmap(SigBit(sig, i)))); + + SigSpec count = balanced_sum(bits, count_width); + SigSpec packed; + for (int i = 0; i < width; i++) + packed.append(emit_gt(count, i, count_width)); + + module->connect(SigSpec(sig2), packed); + remove_old_cells(old_cells); + + log(" Forward dense pack: %s -> %s, width=%d, count_width=%d.\n", + log_id(sig->name), log_id(sig2->name), width, count_width); + forward_rewrites++; + return true; + } + + bool rewrite_reverse_suffix_read() + { + Wire *disable = port("disable_in"); + Wire *data = port("data_in"); + Wire *mask = port("mask"); + if (!disable || !data || !mask) + return false; + if (!disable->port_input || !data->port_input || !mask->port_output) + return false; + if (GetSize(disable) != GetSize(data) || GetSize(mask) != GetSize(data)) + return false; + if (GetSize(module->ports) != 3) + return false; + + int dec_count = std::max(count_binop_const(ID($sub), 1), + count_binop_const(ID($add), -1)); + int loop_width = dec_count + 1; + if (loop_width < 4 || loop_width > max_width || loop_width > GetSize(data)) + return false; + if (count_cells(ID($mux)) < loop_width) + return false; + if (has_binop_const_other_than(ID($sub), 1) || + has_binop_const_other_than(ID($add), -1)) + return false; + if (!bmux_selects_stay_in_range(data, loop_width)) + return false; + if (!eval_sig_is_zero(SigSpec(mask))) + return false; + + vector old_cells(module->cells().begin(), module->cells().end()); + ref_cell = old_cells.front(); + + int count_width = ceil_log2_int(loop_width + 1); + vector valid(loop_width); + for (int i = 0; i < loop_width; i++) + valid[i] = emit_not(sigmap(SigBit(disable, i))); + + SigSpec out_bits; + for (int j = 0; j < loop_width; j++) { + vector suffix_terms; + for (int k = j + 1; k < loop_width; k++) + suffix_terms.push_back(SigSpec(valid[k])); + SigSpec suffix_count = balanced_sum(suffix_terms, count_width); + + SigSpec candidates; + for (int k = 0; k < loop_width; k++) { + int needed_count = loop_width - 1 - k; + SigBit is_source = emit_eq(suffix_count, needed_count, count_width); + SigBit gated_data = emit_and(sigmap(SigBit(data, k)), is_source); + candidates.append(gated_data); + } + + SigBit selected = emit_reduce_or(candidates); + out_bits.append(emit_and(valid[j], selected)); + } + + if (GetSize(mask) > loop_width) + out_bits.append(SigSpec(State::S0, GetSize(mask) - loop_width)); + + module->connect(SigSpec(mask), out_bits); + remove_old_cells(old_cells); + + log(" Reverse suffix read: %s/%s -> %s, loop_width=%d, count_width=%d.\n", + log_id(disable->name), log_id(data->name), log_id(mask->name), + loop_width, count_width); + reverse_rewrites++; + return true; + } + + void run() + { + if (module->has_processes_warn()) + return; + if (rewrite_forward_dense_pack()) + return; + rewrite_reverse_suffix_read(); + } +}; + +struct OptCompactPrefixPass : public Pass +{ + OptCompactPrefixPass() : Pass("opt_compact_prefix", + "rewrite monotonic compaction loops into balanced prefix/routing logic") {} + + void help() override + { + log("\n"); + log(" opt_compact_prefix [options] [selection]\n"); + log("\n"); + log("Recognize narrow monotonic compaction patterns produced by frontend\n"); + log("lowering of SystemVerilog loops and replace their long loop-carried\n"); + log("index/update cones with balanced prefix-count and routing logic.\n"); + log("\n"); + log("Currently this pass handles the dense bit-pack and reverse suffix-read\n"); + log("forms used by the qor_spi_ra_add_chain and qor_spi_ra_sub_chain\n"); + log("regressions. Non-matching modules are left unchanged.\n"); + log("\n"); + log(" -max_width \n"); + log(" Maximum compaction width to rewrite. Default: 64.\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing OPT_COMPACT_PREFIX pass (monotonic compaction rewrites).\n"); + + int max_width = 64; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-max_width" && argidx + 1 < args.size()) { + max_width = atoi(args[++argidx].c_str()); + continue; + } + break; + } + extra_args(args, argidx, design); + + int total_forward = 0; + int total_reverse = 0; + int total_removed = 0; + int total_emitted = 0; + + for (auto module : design->selected_modules()) { + OptCompactPrefixWorker worker(module, max_width); + worker.run(); + total_forward += worker.forward_rewrites; + total_reverse += worker.reverse_rewrites; + total_removed += worker.old_cells_removed; + total_emitted += worker.new_cells_emitted; + } + + log("Rewrote %d forward pack(s), %d reverse suffix read(s); " + "removed %d old cell(s), emitted %d new cell(s).\n", + total_forward, total_reverse, total_removed, total_emitted); + + if (total_forward || total_reverse) + Yosys::run_pass("clean -purge"); + } +} OptCompactPrefixPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/opt/opt_balance_tree.ys b/tests/opt/opt_balance_tree.ys index 6f8b8b711..36f106a03 100644 --- a/tests/opt/opt_balance_tree.ys +++ b/tests/opt/opt_balance_tree.ys @@ -42,6 +42,183 @@ log -pop +# Test 31 +log -header "Sliced shifted ADD chain" +log -push +design -reset +read_verilog -icells <