From e39395132dc00b652a6a119e47c923cdabd5eeb0 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Wed, 27 May 2026 00:39:25 -0700 Subject: [PATCH 1/2] opt_addcin pass --- passes/opt/Makefile.inc | 1 + passes/opt/opt_addcin.cc | 277 ++++++++++++++++++++++++++++ tests/opt/opt_addcin.ys | 381 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 passes/opt/opt_addcin.cc create mode 100644 tests/opt/opt_addcin.ys diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 6d932bbca..327c6bfc4 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -25,6 +25,7 @@ OBJS += passes/opt/muxpack.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_addcin.cc b/passes/opt/opt_addcin.cc new file mode 100644 index 000000000..dd61f4666 --- /dev/null +++ b/passes/opt/opt_addcin.cc @@ -0,0 +1,277 @@ +/* + * 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" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct OptAddcinWorker +{ + struct Leaf { + SigSpec sig; + bool is_signed; + }; + + struct Rewrite { + Cell *outer; + Cell *inner; + Leaf a; + Leaf b; + SigSpec cin; + SigSpec y; + int width; + }; + + Module *module; + SigMap sigmap; + dict add_driver; + pool tracked_output_bits; + dict input_users; + pool claimed; + vector rewrites; + + OptAddcinWorker(Module *module) : module(module), sigmap(module) + { + } + + void build_indexes() + { + for (auto cell : module->selected_cells()) { + if (cell->type != ID($add)) + continue; + SigSpec y = sigmap(cell->getPort(ID::Y)); + add_driver[y] = cell; + for (auto bit : y) + tracked_output_bits.insert(bit); + } + + for (auto wire : module->wires()) { + if (!wire->port_output) + continue; + for (auto bit : sigmap(wire)) + if (tracked_output_bits.count(bit)) + input_users[bit]++; + } + + for (auto cell : module->cells()) { + for (auto &conn : cell->connections()) { + if (!cell->input(conn.first)) + continue; + for (auto bit : sigmap(conn.second)) + if (tracked_output_bits.count(bit)) + input_users[bit]++; + } + } + } + + Cell *driver_of(const SigSpec &sig) + { + auto it = add_driver.find(sig); + return it == add_driver.end() ? nullptr : it->second; + } + + bool has_single_use(const SigSpec &sig) + { + for (auto bit : sig) { + auto it = input_users.find(bit); + if (it == input_users.end() || it->second != 1) + return false; + } + return true; + } + + bool collect(Cell *outer) + { + if (claimed.count(outer) || outer->type != ID($add)) + return false; + + SigSpec outer_a = sigmap(outer->getPort(ID::A)); + SigSpec outer_b = sigmap(outer->getPort(ID::B)); + SigSpec outer_y = sigmap(outer->getPort(ID::Y)); + Cell *inner_a = driver_of(outer_a); + Cell *inner_b = driver_of(outer_b); + + if ((inner_a != nullptr) == (inner_b != nullptr)) + return false; + + Cell *inner = inner_a ? inner_a : inner_b; + if (inner == outer || claimed.count(inner) || inner->type != ID($add)) + return false; + + SigSpec inner_y = sigmap(inner->getPort(ID::Y)); + SigSpec inner_operand = inner_a ? outer_a : outer_b; + if (inner_y != inner_operand) + return false; + + int width = GetSize(outer_y); + if (width == 0 || GetSize(inner_y) != width) + return false; + if (inner->getParam(ID::Y_WIDTH).as_int() != width) + return false; + if (outer->getParam(ID::Y_WIDTH).as_int() != width) + return false; + if (!has_single_use(inner_y)) + return false; + + vector leaves; + leaves.push_back({sigmap(inner->getPort(ID::A)), inner->getParam(ID::A_SIGNED).as_bool()}); + leaves.push_back({sigmap(inner->getPort(ID::B)), inner->getParam(ID::B_SIGNED).as_bool()}); + if (inner_a) + leaves.push_back({outer_b, outer->getParam(ID::B_SIGNED).as_bool()}); + else + leaves.push_back({outer_a, outer->getParam(ID::A_SIGNED).as_bool()}); + + int cin_index = -1; + for (int i = 0; i < GetSize(leaves); i++) { + if (GetSize(leaves[i].sig) > width) + return false; + if (GetSize(leaves[i].sig) == 1 && !leaves[i].is_signed) { + if (cin_index != -1) + return false; + cin_index = i; + } + } + if (cin_index == -1) + return false; + + vector operands; + for (int i = 0; i < GetSize(leaves); i++) { + if (i == cin_index) + continue; + operands.push_back(leaves[i]); + } + log_assert(GetSize(operands) == 2); + + claimed.insert(outer); + claimed.insert(inner); + rewrites.push_back({outer, inner, operands[0], operands[1], leaves[cin_index].sig, outer_y, width}); + return true; + } + + SigSpec extend_leaf(const Leaf &leaf, int width) + { + SigSpec sig = leaf.sig; + sig.extend_u0(width, leaf.is_signed); + return sig; + } + + void apply(const Rewrite &rewrite) + { + Cell *cell = rewrite.outer; + + SigSpec a = extend_leaf(rewrite.a, rewrite.width); + SigSpec b = extend_leaf(rewrite.b, rewrite.width); + + SigSpec wide_a(State::S1); + wide_a.append(a); + + SigSpec wide_b = rewrite.cin; + wide_b.append(b); + + Wire *wide_y_wire = module->addWire(NEW_ID2_SUFFIX("addcin_y"), rewrite.width + 1); + SigSpec wide_y(wide_y_wire); + Cell *wide_add = module->addCell(NEW_ID2_SUFFIX("addcin"), ID($add)); + wide_add->attributes = rewrite.outer->attributes; + wide_add->setPort(ID::A, wide_a); + wide_add->setPort(ID::B, wide_b); + wide_add->setPort(ID::Y, wide_y); + wide_add->setParam(ID::A_SIGNED, false); + wide_add->setParam(ID::B_SIGNED, false); + wide_add->fixup_parameters(); + + module->connect(rewrite.y, wide_y.extract(1, rewrite.width)); + module->remove(rewrite.outer); + module->remove(rewrite.inner); + } + + int run() + { + build_indexes(); + + vector cells; + for (auto cell : module->selected_cells()) + cells.push_back(cell); + for (auto cell : cells) + collect(cell); + + for (auto &rewrite : rewrites) + apply(rewrite); + + return GetSize(rewrites); + } +}; + +struct OptAddcinPass : public Pass { + OptAddcinPass() : Pass("opt_addcin", "rewrite add-with-carry-in patterns as widened adders") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" opt_addcin [selection]\n"); + log("\n"); + log("This pass rewrites a conservative two-$add, three-leaf carry-in pattern\n"); + log("into a single widened $add:\n"); + log("\n"); + log(" (A + B) + CI -> ({A_ext, 1'b1} + {B_ext, CI})[WIDTH:1]\n"); + log("\n"); + log("Only one leaf may be a one-bit unsigned carry input. The other two leaves\n"); + log("are explicitly sign- or zero-extended to the shared add width before the\n"); + log("widened add is emitted. The pass rejects $sub/$alu patterns, mismatched\n"); + log("intermediate/final widths, multi-bit carry operands, signed carry operands,\n"); + log("and intermediate sums with fanout outside the final add.\n"); + log("\n"); + log("This pass is not invoked by the default 'opt' or 'peepopt' scripts. It is\n"); + log("intended for flows that want to map carry-in adds onto adders without an\n"); + log("explicit carry input. Run add-chain timing transforms such as\n"); + log("'opt_balance_tree -arith' or 'opt_parallel_prefix -arith' before this pass,\n"); + log("then run 'alumacc' or technology mapping afterwards.\n"); + log("\n"); + log("Runtime is linear in the number of module connections plus the total width\n"); + log("of rewritten candidates. The implementation builds driver and input-use\n"); + log("indexes once per module and does not scan all cells per candidate.\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing OPT_ADDCIN pass (carry-in add widening).\n"); + + size_t argidx = 1; + extra_args(args, argidx, design); + + int total = 0; + for (auto module : design->selected_modules()) { + OptAddcinWorker worker(module); + int count = worker.run(); + total += count; + if (count) + log("Rewrote %d add-carry-in pattern%s in module %s.\n", + count, count == 1 ? "" : "s", log_id(module)); + } + + if (total) + design->scratchpad_set_bool("opt.did_something", true); + log("Rewrote %d add-carry-in pattern%s total.\n", total, total == 1 ? "" : "s"); + } +} OptAddcinPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/opt/opt_addcin.ys b/tests/opt/opt_addcin.ys new file mode 100644 index 000000000..5a7592542 --- /dev/null +++ b/tests/opt/opt_addcin.ys @@ -0,0 +1,381 @@ +# Tests for opt_addcin. +# +# opt_addcin recognizes a conservative two-$add, three-leaf pattern that +# represents A + B + cin with a one-bit unsigned carry input. It rewrites the +# two adders into one WIDTH+1 adder whose artificial LSB produces the carry into +# the real bit 0. Each test below states the exact RTLIL shape being built and +# why the pass should either rewrite it or leave it alone. + +# ============================================================================ +# Group A: Positive rewrites +# ============================================================================ + +# Test A1: unsigned carry on the B port of the final add. +# +# Shape: +# add_ab : s = a + b +# add_cin : y = s + cin +# +# The intermediate and final add widths are both 4, and cin is exactly one +# unsigned bit, so the pass should replace both adders with one 5-bit $add. +log -header "A1: unsigned carry on final-add B port" +log -push +design -reset +read_verilog -icells < Date: Wed, 27 May 2026 01:51:54 -0700 Subject: [PATCH 2/2] Fixup --- passes/opt/opt_addcin.cc | 20 ++++++++++++++++++- tests/opt/opt_addcin.ys | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/passes/opt/opt_addcin.cc b/passes/opt/opt_addcin.cc index dd61f4666..4ecebde21 100644 --- a/passes/opt/opt_addcin.cc +++ b/passes/opt/opt_addcin.cc @@ -23,6 +23,24 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +static void merge_add_attributes(Cell *dst, const Cell *outer, const Cell *inner) +{ + dst->attributes = outer->attributes; + + for (const auto &attr : inner->attributes) + if (attr.first != ID::src && !dst->attributes.count(attr.first)) + dst->attributes[attr.first] = attr.second; + + std::string outer_src = outer->get_src_attribute(); + std::string inner_src = inner->get_src_attribute(); + if (outer_src.empty()) { + if (!inner_src.empty()) + dst->set_src_attribute(inner_src); + } else if (!inner_src.empty() && inner_src != outer_src) { + dst->set_src_attribute(outer_src + "|" + inner_src); + } +} + struct OptAddcinWorker { struct Leaf { @@ -189,7 +207,7 @@ struct OptAddcinWorker Wire *wide_y_wire = module->addWire(NEW_ID2_SUFFIX("addcin_y"), rewrite.width + 1); SigSpec wide_y(wide_y_wire); Cell *wide_add = module->addCell(NEW_ID2_SUFFIX("addcin"), ID($add)); - wide_add->attributes = rewrite.outer->attributes; + merge_add_attributes(wide_add, rewrite.outer, rewrite.inner); wide_add->setPort(ID::A, wide_a); wide_add->setPort(ID::B, wide_b); wide_add->setPort(ID::Y, wide_y); diff --git a/tests/opt/opt_addcin.ys b/tests/opt/opt_addcin.ys index 5a7592542..cfd70029e 100644 --- a/tests/opt/opt_addcin.ys +++ b/tests/opt/opt_addcin.ys @@ -379,3 +379,45 @@ design -load postopt select -assert-count 3 t:$add design -reset log -pop + +# ============================================================================ +# Group D: Metadata preservation +# ============================================================================ + +# Test D1: replacement adder preserves attributes from both consumed adders. +# +# Shape: +# add_ab : s = a + b (* src="inner_add.v:10.1-10.8", inner_marker=1 *) +# add_cin : y = s + cin (* src="outer_add.v:20.1-20.8", outer_marker=1 *) +# +# The replacement cell is the only remaining $add. It should keep the outer +# cell's attributes, import inner-only metadata, and retain both source +# locations in its merged src attribute. +log -header "D1: merge attributes from inner and outer cells" +log -push +design -reset +read_verilog -icells <