From b49f3219231eef5ea3279a5695f73605e3fedfe2 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:56 +1200 Subject: [PATCH 1/7] 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 bb49b0be32e2d83334a46123aea90a43043a02b1 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:56 +1200 Subject: [PATCH 2/7] 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 1b9caf4f2c152c7aaaaf5f6ca7c1174ac329338d Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:57 +1200 Subject: [PATCH 3/7] 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 fb560a560f640845a9e23f96473544985bdaf326 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:57 +1200 Subject: [PATCH 4/7] 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 2f8cf84882848dfb59b24b340b158bd08252c2fc Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:57 +1200 Subject: [PATCH 5/7] 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 9edae972afa1e81ec03f5a2d6812d9ea2f1ae1a1 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:58 +1200 Subject: [PATCH 6/7] 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 bafaa2e95cd2a7ab935269a6aaf244daebba7144 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:58 +1200 Subject: [PATCH 7/7] 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)