From 66654d278d02a72f8888077f422cc4d3e9e106fd Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:37:03 +1200 Subject: [PATCH 1/8] tests/opt: Add extra alumacc test Checks equivalence when folding an `$lt` (or equivalently a `$gt`) into a `$sub`. Currently failing, fix incoming. --- tests/opt/alumacc.ys | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/opt/alumacc.ys b/tests/opt/alumacc.ys index 043bdaaee..bdb96321b 100644 --- a/tests/opt/alumacc.ys +++ b/tests/opt/alumacc.ys @@ -31,3 +31,15 @@ proc equiv_opt -assert alumacc alumacc select -assert-count 1 t:$alu + +design -reset +read_verilog << EOT +module top(input signed a, b, output x, output [1:0] y); +assign y = $unsigned(a) - $unsigned(b); +assign x = a > b; +endmodule +EOT +hierarchy +wreduce +equiv_opt -assert alumacc + From 1af8ded0cb98d9610b624e880d4c3e6f6c0a6c8e Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:41:31 +1200 Subject: [PATCH 2/8] alumacc: Fix get_lt for oversized output Current behavior uses the output width to determine the MSb(s), however this is not correct if the output is larger than the inputs (which doesn't happen when creating an `$alu` for a compare op, but does when folding a compare op into an arithmetic `$alu`). Co-authored-by: Lofty --- passes/techmap/alumacc.cc | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/passes/techmap/alumacc.cc b/passes/techmap/alumacc.cc index 5cce8c0ee..e42585874 100644 --- a/passes/techmap/alumacc.cc +++ b/passes/techmap/alumacc.cc @@ -41,18 +41,22 @@ struct AlumaccWorker std::vector cells; RTLIL::SigSpec a, b, c, y; std::vector> cmp; - bool is_signed, invert_b; + bool is_signed, invert_b, oversized; RTLIL::Cell *alu_cell; RTLIL::SigSpec cached_lt, cached_slt, cached_gt, cached_sgt, cached_eq, cached_ne; RTLIL::SigSpec cached_cf, cached_of, cached_sf; - RTLIL::SigSpec get_lt(bool is_signed) { - if (is_signed) { + RTLIL::SigSpec get_lt(bool this_signed) { + if (this_signed) { if (GetSize(cached_slt) == 0) { - get_of(); get_sf(); - cached_slt = alu_cell->module->Xor(NEW_ID, cached_of, cached_sf); + if (is_signed && oversized) { + cached_slt = cached_sf; + } else { + get_of(); + cached_slt = alu_cell->module->Xor(NEW_ID, cached_of, cached_sf); + } } return cached_slt; @@ -65,10 +69,10 @@ struct AlumaccWorker } } - RTLIL::SigSpec get_gt(bool is_signed) { - if (is_signed) { + RTLIL::SigSpec get_gt(bool this_signed) { + if (this_signed) { if (GetSize(cached_sgt) == 0) { - get_lt(is_signed); + get_lt(this_signed); get_eq(); SigSpec Or = alu_cell->module->Or(NEW_ID, cached_slt, cached_eq); cached_sgt = alu_cell->module->Not(NEW_ID, Or, false, alu_cell->get_src_attribute()); @@ -77,7 +81,7 @@ struct AlumaccWorker return cached_sgt; } else { if (GetSize(cached_gt) == 0) { - get_lt(is_signed); + get_lt(this_signed); get_eq(); SigSpec Or = alu_cell->module->Or(NEW_ID, cached_lt, cached_eq); cached_gt = alu_cell->module->Not(NEW_ID, Or, false, alu_cell->get_src_attribute()); @@ -112,7 +116,14 @@ struct AlumaccWorker if (GetSize(cached_of) == 0) { cached_of = {alu_cell->getPort(ID::CO), alu_cell->getPort(ID::CI)}; log_assert(GetSize(cached_of) >= 2); - cached_of = alu_cell->module->Xor(NEW_ID, cached_of[GetSize(cached_of)-1], cached_of[GetSize(cached_of)-2]); + if (oversized) { + auto max_input = max(GetSize(a), GetSize(b)); + log_assert(max_input >= 1); + cached_of = alu_cell->module->Xor(NEW_ID, cached_of[max_input], cached_of[max_input-1]); + } else { + log_assert(GetSize(y) >= 1); + cached_of = alu_cell->module->Xor(NEW_ID, cached_of[GetSize(y)], cached_of[GetSize(y)-1]); + } } return cached_of; } @@ -120,7 +131,12 @@ struct AlumaccWorker RTLIL::SigSpec get_sf() { if (GetSize(cached_sf) == 0) { cached_sf = alu_cell->getPort(ID::Y); - cached_sf = cached_sf[GetSize(cached_sf)-1]; + log_assert(GetSize(cached_sf) >= 1); + if (oversized && !is_signed) { + cached_sf = cached_sf[max(GetSize(a), GetSize(b))-1]; + } else { + cached_sf = cached_sf[GetSize(y)-1]; + } } return cached_sf; } @@ -357,6 +373,7 @@ struct AlumaccWorker alunode->cells.push_back(n->cell); alunode->is_signed = a_signed; alunode->invert_b = subtract_b; + alunode->oversized = max(GetSize(A), GetSize(B)) < GetSize(n->y); alunode->a = A; alunode->b = B; @@ -448,6 +465,7 @@ struct AlumaccWorker n->y = module->addWire(NEW_ID, max(GetSize(A), GetSize(B))); n->is_signed = is_signed; n->invert_b = true; + n->oversized = false; sig_alu[RTLIL::SigSig(A, B)].insert(n); log(" new $alu\n"); } else { From ea20a8b4772c94688baea5684d2a13cdbb9640a9 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:43:08 +1200 Subject: [PATCH 3/8] tests/alumacc: Move opt/temp/alumacc Because apparently there's a whole `tests/alumacc` folder. --- tests/{opt/alumacc.ys => alumacc/cmp_merge.ys} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{opt/alumacc.ys => alumacc/cmp_merge.ys} (100%) diff --git a/tests/opt/alumacc.ys b/tests/alumacc/cmp_merge.ys similarity index 100% rename from tests/opt/alumacc.ys rename to tests/alumacc/cmp_merge.ys From 8962b6b6db1cd010af8038f49d27e19f21bcc824 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:49:58 +1200 Subject: [PATCH 4/8] tests/alumacc: Minor optimizations --- tests/alumacc/cmp_merge.ys | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/alumacc/cmp_merge.ys b/tests/alumacc/cmp_merge.ys index bdb96321b..ae46ba883 100644 --- a/tests/alumacc/cmp_merge.ys +++ b/tests/alumacc/cmp_merge.ys @@ -26,10 +26,8 @@ assign ne = ra != rb; endmodule EOT -proc - equiv_opt -assert alumacc -alumacc +design -load postopt select -assert-count 1 t:$alu design -reset From 7c050b8495bfb12d84bc7c6d1263145f4416d4ad Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:06:58 +1200 Subject: [PATCH 5/8] alumacc: cmp_mergeable function Replace all the existing mergeable checks with a call to a single function. Fix remaining discrepencies (undersized output and mismatched signed inputs). --- passes/techmap/alumacc.cc | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/passes/techmap/alumacc.cc b/passes/techmap/alumacc.cc index e42585874..2a1fcb9f9 100644 --- a/passes/techmap/alumacc.cc +++ b/passes/techmap/alumacc.cc @@ -427,6 +427,24 @@ struct AlumaccWorker eq_cells.push_back(cell); } + auto cmp_mergeable = [](const alunode_t *node, const SigSpec cmp_A, const SigSpec cmp_B, bool is_signed) { + if (!node->invert_b || node->c != State::S1) { + // comparators need subtraction + return false; + } + if (max(GetSize(cmp_A), GetSize(cmp_B)) > GetSize(node->y)) { + // must have at least as many output bits + return false; + } + if (is_signed != node->is_signed) { + if (GetSize(cmp_A) != GetSize(cmp_B)) { + // mismatched input widths are problematic when mixing signedness + return false; + } + } + return true; + }; + for (auto cell : lge_cells) { log(" creating $alu model for %s (%s):", cell, cell->type.unescape()); @@ -442,14 +460,14 @@ struct AlumaccWorker alunode_t *n = nullptr; for (auto node : sig_alu[RTLIL::SigSig(A, B)]) - if (node->invert_b && node->c == State::S1) { + if (cmp_mergeable(node, A, B, is_signed)) { n = node; break; } if (n == nullptr) { for (auto node : sig_alu[RTLIL::SigSig(B, A)]) - if (node->is_signed == is_signed && node->invert_b && node->c == State::S1) { + if (cmp_mergeable(node, B, A, is_signed)) { n = node; cmp_less = !cmp_less; std::swap(A, B); @@ -488,14 +506,14 @@ struct AlumaccWorker alunode_t *n = nullptr; for (auto node : sig_alu[RTLIL::SigSig(A, B)]) - if (node->invert_b && node->c == State::S1) { + if (cmp_mergeable(node, A, B, is_signed)) { n = node; break; } if (n == nullptr) { for (auto node : sig_alu[RTLIL::SigSig(B, A)]) - if (node->is_signed == is_signed && node->invert_b && node->c == State::S1) { + if (cmp_mergeable(node, B, A, is_signed)) { n = node; break; } From f01c1f86a5d20e543bdfd33d7e8051d088756753 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:11:45 +1200 Subject: [PATCH 6/8] tests/alumacc: Add broad merge test Mixes input cells, signedness, input width, output width. Within the things that are `random.choice`, they *should* always be equivalent, but add a seed arg anyway just in case one day they're not. --- tests/alumacc/.gitignore | 1 + tests/alumacc/generate_mk.py | 96 ++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/alumacc/.gitignore diff --git a/tests/alumacc/.gitignore b/tests/alumacc/.gitignore new file mode 100644 index 000000000..90ea6e92a --- /dev/null +++ b/tests/alumacc/.gitignore @@ -0,0 +1 @@ +uut_*.* diff --git a/tests/alumacc/generate_mk.py b/tests/alumacc/generate_mk.py index 6a921d5a0..d11ef1f47 100644 --- a/tests/alumacc/generate_mk.py +++ b/tests/alumacc/generate_mk.py @@ -5,4 +5,100 @@ sys.path.append("..") import gen_tests_makefile +import argparse +import random +from itertools import product + +TEMPLATE=r"""read_rtlil << EOT +module \top + wire width {a_width} input 1 signed \a + wire width {b_width} input 2 signed \b + wire output 3 \x + wire width {y_width} output 4 \y + cell ${c1} \c1 + parameter \A_SIGNED {c1_signed} + parameter \B_SIGNED {c1_signed} + parameter \A_WIDTH {a_width} + parameter \B_WIDTH {b_width} + parameter \Y_WIDTH 1 + connect \A \a + connect \B \b + connect \Y \x + end + cell ${c2} \c2 + parameter \A_SIGNED {c2_signed} + parameter \B_SIGNED {c2_signed} + parameter \A_WIDTH {a_width} + parameter \B_WIDTH {b_width} + parameter \Y_WIDTH {y_width} + connect \A \a + connect \B \b + connect \Y \y + end +end +EOT +equiv_opt -assert alumacc +design -load postopt +{postopt_assert} +""" + +EQ = ["eq", "eqx", "ne", "nex"] +LGE = ["lt", "gt"] +CMP = EQ + LGE +ARITH = ["add", "sub"] +SIGNED = [0, 1] +WIDTH = list(range(1, 4)) + +parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('-S', '--seed', type = int, help = 'seed for PRNG') +args = parser.parse_args() + +seed = args.seed +if seed is None: + seed = random.randrange(sys.maxsize) +print("alumacc PRNG seed: %d" % seed) +random.seed(seed) + +count = 0 +for c1_signed, c2, c2_signed, a_width in product( + SIGNED, [EQ, LGE, ARITH], SIGNED, WIDTH +): + c1 = random.choice(LGE) + if isinstance(c2, list): + c2 = random.choice(c2) + + b_widths: list[int] + if a_width == 2: + b_widths = [1, 2, 3] + else: + b_widths = [a_width] + y_widths: list[int] + if c2 in CMP: + y_widths = [1] + else: + y_widths = b_widths + + for b_width, y_width in product(b_widths, y_widths): + out_cells = "t:$alu" + for c in c1, c2: + if c in EQ: + # LGE cells will always remap to $alu, but EQ cells only will if merged + out_cells += f" t:${c}" + + undersized = c2 in ARITH and y_width < max(a_width, b_width) + mixed_sign = c1_signed != c2_signed and a_width != b_width + if c2 != "add" and not undersized and not mixed_sign: + # cells correctly merged + postopt_assert = f"select -assert-count 1 {out_cells}" + else: + # postopt_assert = f"select -assert-count 2 {out_cells}" + # while currently correct, the above assertion would block potential improvements + postopt_assert = "" + + with open(f"uut_{'s' if c1_signed else 'u'}{c1}_{'s' if c2_signed else 'u'}{c2}_{a_width}_{b_width}_{y_width}.ys", "w") as f: + print(str.format_map(TEMPLATE, locals()), file=f) + count = count + 1 + +# print(count) + gen_tests_makefile.generate(["--yosys-scripts"]) From 2942aa76f52ec71f1c98ae64be728fc56895c2b9 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:23:46 +1200 Subject: [PATCH 7/8] tests/alumacc: Use assert-max --- tests/alumacc/generate_mk.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/alumacc/generate_mk.py b/tests/alumacc/generate_mk.py index d11ef1f47..997870aa4 100644 --- a/tests/alumacc/generate_mk.py +++ b/tests/alumacc/generate_mk.py @@ -91,9 +91,8 @@ for c1_signed, c2, c2_signed, a_width in product( # cells correctly merged postopt_assert = f"select -assert-count 1 {out_cells}" else: - # postopt_assert = f"select -assert-count 2 {out_cells}" - # while currently correct, the above assertion would block potential improvements - postopt_assert = "" + # currently these all produce 2, but assert-max allows for future improvement + postopt_assert = f"select -assert-max 2 {out_cells}" with open(f"uut_{'s' if c1_signed else 'u'}{c1}_{'s' if c2_signed else 'u'}{c2}_{a_width}_{b_width}_{y_width}.ys", "w") as f: print(str.format_map(TEMPLATE, locals()), file=f) From c1450b54e417544813b2390fa4c0782660908ed8 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:25:36 +1200 Subject: [PATCH 8/8] alumacc: Move undersize check Don't add undersized arithmetic to the pool of mergeable nodes (though a `wreduce` prior should make undersized output impossible). --- passes/techmap/alumacc.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/passes/techmap/alumacc.cc b/passes/techmap/alumacc.cc index 2a1fcb9f9..29ec094b4 100644 --- a/passes/techmap/alumacc.cc +++ b/passes/techmap/alumacc.cc @@ -380,7 +380,10 @@ struct AlumaccWorker alunode->c = C; alunode->y = n->y; - sig_alu[RTLIL::SigSig(A, B)].insert(alunode); + // only consider merging if the output is at least as wide as the inputs + if (max(GetSize(A), GetSize(B)) <= GetSize(n->y)) { + sig_alu[RTLIL::SigSig(A, B)].insert(alunode); + } delete_nodes.insert(n); next_macc:; } @@ -432,10 +435,6 @@ struct AlumaccWorker // comparators need subtraction return false; } - if (max(GetSize(cmp_A), GetSize(cmp_B)) > GetSize(node->y)) { - // must have at least as many output bits - return false; - } if (is_signed != node->is_signed) { if (GetSize(cmp_A) != GetSize(cmp_B)) { // mismatched input widths are problematic when mixing signedness