From ccf39099826b7160d1283766bbaee19d48c8dd2e Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Thu, 11 Sep 2025 03:28:50 -0700 Subject: [PATCH 1/8] Initial implementation of addsub_c with no intermediate fanout allowed --- passes/opt/Makefile.inc | 1 + passes/opt/peepopt.cc | 3 + passes/opt/peepopt_addsub_c.pmg | 129 +++++ tests/peepopt/addsub_c.ys | 954 ++++++++++++++++++++++++++++++++ 4 files changed, 1087 insertions(+) create mode 100644 passes/opt/peepopt_addsub_c.pmg create mode 100644 tests/peepopt/addsub_c.ys diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index c388bb8da..96566c588 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -33,6 +33,7 @@ PEEPOPT_PATTERN += passes/opt/peepopt_shiftmul_left.pmg PEEPOPT_PATTERN += passes/opt/peepopt_shiftadd.pmg PEEPOPT_PATTERN += passes/opt/peepopt_muldiv.pmg PEEPOPT_PATTERN += passes/opt/peepopt_muldiv_c.pmg +PEEPOPT_PATTERN += passes/opt/peepopt_addsub_c.pmg PEEPOPT_PATTERN += passes/opt/peepopt_muxorder.pmg PEEPOPT_PATTERN += passes/opt/peepopt_formal_clockgateff.pmg PEEPOPT_PATTERN += passes/opt/peepopt_sub_neg.pmg diff --git a/passes/opt/peepopt.cc b/passes/opt/peepopt.cc index c2597cbfc..1ef160191 100644 --- a/passes/opt/peepopt.cc +++ b/passes/opt/peepopt.cc @@ -51,6 +51,8 @@ struct PeepoptPass : public Pass { log("\n"); log("This pass employs the following rules by default:\n"); log("\n"); + log(" * addsub_c - Replace (A+-B)+-C with A+(B+-C) when B and C are constants.\n"); + log("\n"); log(" * muldiv - Replace (A*B)/B with A\n"); log("\n"); log(" * muldiv_c - Replace (A*B)/C with A*(B/C) when C is a const divisible by B.\n"); @@ -127,6 +129,7 @@ struct PeepoptPass : public Pass { pm.run_shiftmul_left(); pm.run_muldiv(); pm.run_muldiv_c(); + pm.run_addsub_c(); pm.run_sub_neg(); if (muxorder) pm.run_muxorder(); diff --git a/passes/opt/peepopt_addsub_c.pmg b/passes/opt/peepopt_addsub_c.pmg new file mode 100644 index 000000000..29e5a4741 --- /dev/null +++ b/passes/opt/peepopt_addsub_c.pmg @@ -0,0 +1,129 @@ +pattern addsub_c +// +// Authored by Akash Levy of Silimate, Inc. under ISC license. +// Transforms add->add/sub_c into add_c canceling the constants out: +// y = (a +- b_const) +- c_const ===> a +- eval(b_const +- c_const) +// + +state a b_const addsub1_y + +match addsub1 + // Select add/sub + select addsub1->type.in($add, $sub) +endmatch + +code a b_const addsub1_y + // Get adder signals + a = port(addsub1, \A); + b_const = port(addsub1, \B); + addsub1_y = port(addsub1, \Y); + + // Fanout of each add/sub Y bit should be 1 (no bit-split) + if (nusers(addsub1_y) != 2) + reject; + + // A and B can be interchanged for adder + if (addsub1->type == $add) { + branch; + std::swap(a, b_const); + } +endcode + +match addsub2 + // Select add/sub of form (a +- b_const) +- c_const + select addsub2->type.in($add, $sub) + + // Check that b_const and c_const is constant + filter b_const.is_fully_const() + filter port(addsub2, \B).is_fully_const() + index remove_bottom_padding(port(addsub2, \A)) === addsub1_y +endmatch + +code + // Get addsub2 signals + SigSpec addsub2_a = port(addsub2, \A); + SigSpec c_const = port(addsub2, \B); + SigSpec addsub2_y = port(addsub2, \Y); + + // Get offset of addsub1 result chunk in addsub2 + int offset = GetSize(addsub2_a) - GetSize(addsub1_y); + + // Get properties and values of b_const and c_const + // b_const may be coming from the A port + // But it is an RTLIL invariant that A_SIGNED equals B_SIGNED + bool b_const_signed = addsub1->getParam(ID::B_SIGNED).as_bool(); + bool c_const_signed = addsub2->getParam(ID::B_SIGNED).as_bool(); + int b_const_int = b_const.as_int(b_const_signed); + int c_const_int = c_const.as_int(c_const_signed); + int b_const_int_shifted = b_const_int << offset; + + // Helper lambdas for two's complement math + auto sign2sComplement = [](auto value, int numBits) { + if (value & (1 << (numBits - 1))) { + return -1; + } else { + return 1; + } + }; + auto twosComplement = [](auto value, int numBits) { + if (value & (1 << (numBits - 1))) { + return (~value) + 1; // invert bits before adding 1 + } else { + return value; + } + }; + + // Two's complement conversion + if (b_const_signed) + b_const_int = sign2sComplement(b_const_int, GetSize(b_const)) * twosComplement(b_const_int, GetSize(b_const)); + if (c_const_signed) + c_const_int = sign2sComplement(c_const_int, GetSize(c_const)) * twosComplement(c_const_int, GetSize(c_const)); + // Calculate the constant and compress the width to fit the value + Const const_value; + Const b_const_actual; + b_const_actual = b_const_int_shifted; + b_const_actual.compress(b_const_signed); + + if (addsub1->type == $add) + if (addsub2->type == $add) + const_value = b_const_int_shifted + c_const_int; + else + const_value = b_const_int_shifted - c_const_int; + else + if (addsub2->type == $add) + const_value = b_const_int_shifted - c_const_int; + else + const_value = b_const_int_shifted + c_const_int; + const_value.compress(b_const_signed | c_const_signed); + + // Integer values should be lesser than 32 bits + // This is because we are using C++ types, and int is 32 bits + // FIXME: use long long or BigInteger to make pass work with >32 bits + if (GetSize(addsub1->getParam(ID::B_WIDTH)) > 32) + reject; + if (GetSize(b_const) > 32) + reject; + if (GetSize(c_const) + offset > 32) + reject; + + // // Check for potential overflow + // if (std::max(GetSize(b_const_actual), GetSize(a)) + 1 > GetSize(addsub1_y)) + // reject; + + // Check that there are only zeros before offset + if (offset < 0 || !addsub2_a.extract(0, offset).is_fully_zero()) + reject; + + // Rewire to only keep addsub1 + addsub1->setPort(\A, a); + addsub1->setPort(\B, const_value); + addsub1->setPort(\Y, addsub2_y); + + // Remove addsub2 + autoremove(addsub2); + + // Log, fixup, accept + log("addsub_const pattern in %s: addsub1=%s, addsub2=%s\n", log_id(module), log_id(addsub1), log_id(addsub2)); + addsub1->fixup_parameters(); + accept; +endcode diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys new file mode 100644 index 000000000..e8b8733cf --- /dev/null +++ b/tests/peepopt/addsub_c.ys @@ -0,0 +1,954 @@ +log -header "Test simple positive case 1" +log -push +design -reset +read_verilog < 8'd100; +endmodule +EOF +check -assert +equiv_opt -assert peepopt +design -load postopt +select t:$add -assert-count 1 +select t:$sub -assert-count 0 +select t:$gt -assert-count 1 +design -reset +log -pop + +log -header "Result drives logical operations" +log -push +design -reset +read_verilog <= 255); +endmodule +EOF +check -assert +equiv_opt -assert peepopt +design -load postopt +write_verilog test.v +select t:$add -assert-count 0 +select t:$sub -assert-count 1 +design -reset +log -pop From c1557950ef57bbfbcabe5ddfed5bc941cd9d2938 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Thu, 11 Sep 2025 03:48:14 -0700 Subject: [PATCH 2/8] Support external fanout for addsub_c --- passes/opt/peepopt_addsub_c.pmg | 22 +++++++++++++--------- tests/peepopt/addsub_c.ys | 17 ++++++++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/passes/opt/peepopt_addsub_c.pmg b/passes/opt/peepopt_addsub_c.pmg index 29e5a4741..f2aadcfed 100644 --- a/passes/opt/peepopt_addsub_c.pmg +++ b/passes/opt/peepopt_addsub_c.pmg @@ -18,10 +18,6 @@ code a b_const addsub1_y b_const = port(addsub1, \B); addsub1_y = port(addsub1, \Y); - // Fanout of each add/sub Y bit should be 1 (no bit-split) - if (nusers(addsub1_y) != 2) - reject; - // A and B can be interchanged for adder if (addsub1->type == $add) { branch; @@ -40,6 +36,9 @@ match addsub2 endmatch code + // New addsub1 cell (will be reused unless there is external fanout) + auto cell = addsub1; + // Get addsub2 signals SigSpec addsub2_a = port(addsub2, \A); SigSpec c_const = port(addsub2, \B); @@ -114,16 +113,21 @@ code if (offset < 0 || !addsub2_a.extract(0, offset).is_fully_zero()) reject; - // Rewire to only keep addsub1 - addsub1->setPort(\A, a); - addsub1->setPort(\B, const_value); - addsub1->setPort(\Y, addsub2_y); + // Reuse/create new cell to drive the rewritten equation + if (nusers(addsub1_y) != 2) { + cell = module->addCell(NEW_ID2_SUFFIX("asconst"), addsub1->type); + cell->attributes = addsub1->attributes; + cell->parameters = addsub1->parameters; + } + cell->setPort(\A, a); + cell->setPort(\B, const_value); + cell->setPort(\Y, addsub2_y); // Remove addsub2 autoremove(addsub2); // Log, fixup, accept log("addsub_const pattern in %s: addsub1=%s, addsub2=%s\n", log_id(module), log_id(addsub1), log_id(addsub2)); - addsub1->fixup_parameters(); + cell->fixup_parameters(); accept; endcode diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys index e8b8733cf..c0934dd4b 100644 --- a/tests/peepopt/addsub_c.ys +++ b/tests/peepopt/addsub_c.ys @@ -388,7 +388,7 @@ log -pop -log -header "No transform when addsub1 has a second fanout case 1" +log -header "Transform even when addsub1 has a second fanout case 1" log -push read_verilog < Date: Thu, 11 Sep 2025 04:32:18 -0700 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- tests/peepopt/addsub_c.ys | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys index c0934dd4b..6543de7cf 100644 --- a/tests/peepopt/addsub_c.ys +++ b/tests/peepopt/addsub_c.ys @@ -133,7 +133,7 @@ select -assert-count 1 t:$sub design -reset log -pop -log -header "Signed pattern transformed: (a +- b) +- c case 3" +log -header "Signed pattern transformed: (a +- b) +- c case 4" log -push read_verilog < Date: Thu, 11 Sep 2025 04:37:00 -0700 Subject: [PATCH 4/8] Fixups --- passes/opt/peepopt_addsub_c.pmg | 49 ++++----------------------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/passes/opt/peepopt_addsub_c.pmg b/passes/opt/peepopt_addsub_c.pmg index f2aadcfed..76c6bd74f 100644 --- a/passes/opt/peepopt_addsub_c.pmg +++ b/passes/opt/peepopt_addsub_c.pmg @@ -32,7 +32,7 @@ match addsub2 // Check that b_const and c_const is constant filter b_const.is_fully_const() filter port(addsub2, \B).is_fully_const() - index remove_bottom_padding(port(addsub2, \A)) === addsub1_y + index port(addsub2, \A) === addsub1_y endmatch code @@ -44,9 +44,6 @@ code SigSpec c_const = port(addsub2, \B); SigSpec addsub2_y = port(addsub2, \Y); - // Get offset of addsub1 result chunk in addsub2 - int offset = GetSize(addsub2_a) - GetSize(addsub1_y); - // Get properties and values of b_const and c_const // b_const may be coming from the A port // But it is an RTLIL invariant that A_SIGNED equals B_SIGNED @@ -54,45 +51,19 @@ code bool c_const_signed = addsub2->getParam(ID::B_SIGNED).as_bool(); int b_const_int = b_const.as_int(b_const_signed); int c_const_int = c_const.as_int(c_const_signed); - int b_const_int_shifted = b_const_int << offset; - // Helper lambdas for two's complement math - auto sign2sComplement = [](auto value, int numBits) { - if (value & (1 << (numBits - 1))) { - return -1; - } else { - return 1; - } - }; - auto twosComplement = [](auto value, int numBits) { - if (value & (1 << (numBits - 1))) { - return (~value) + 1; // invert bits before adding 1 - } else { - return value; - } - }; - - // Two's complement conversion - if (b_const_signed) - b_const_int = sign2sComplement(b_const_int, GetSize(b_const)) * twosComplement(b_const_int, GetSize(b_const)); - if (c_const_signed) - c_const_int = sign2sComplement(c_const_int, GetSize(c_const)) * twosComplement(c_const_int, GetSize(c_const)); // Calculate the constant and compress the width to fit the value Const const_value; - Const b_const_actual; - b_const_actual = b_const_int_shifted; - b_const_actual.compress(b_const_signed); - if (addsub1->type == $add) if (addsub2->type == $add) - const_value = b_const_int_shifted + c_const_int; + const_value = b_const_int + c_const_int; else - const_value = b_const_int_shifted - c_const_int; + const_value = b_const_int - c_const_int; else if (addsub2->type == $add) - const_value = b_const_int_shifted - c_const_int; + const_value = b_const_int - c_const_int; else - const_value = b_const_int_shifted + c_const_int; + const_value = b_const_int + c_const_int; const_value.compress(b_const_signed | c_const_signed); // Integer values should be lesser than 32 bits @@ -102,15 +73,7 @@ code reject; if (GetSize(b_const) > 32) reject; - if (GetSize(c_const) + offset > 32) - reject; - - // // Check for potential overflow - // if (std::max(GetSize(b_const_actual), GetSize(a)) + 1 > GetSize(addsub1_y)) - // reject; - - // Check that there are only zeros before offset - if (offset < 0 || !addsub2_a.extract(0, offset).is_fully_zero()) + if (GetSize(c_const) > 32) reject; // Reuse/create new cell to drive the rewritten equation From 8f5b20c423a70d9ce36acccb99e489f96427c8ce Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Thu, 11 Sep 2025 04:46:32 -0700 Subject: [PATCH 5/8] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- tests/peepopt/addsub_c.ys | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys index 6543de7cf..9a6a397cb 100644 --- a/tests/peepopt/addsub_c.ys +++ b/tests/peepopt/addsub_c.ys @@ -81,7 +81,7 @@ log -push read_verilog < Date: Thu, 11 Sep 2025 04:47:28 -0700 Subject: [PATCH 6/8] Small adjustments --- passes/opt/peepopt_addsub_c.pmg | 4 ++-- tests/peepopt/addsub_c.ys | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/passes/opt/peepopt_addsub_c.pmg b/passes/opt/peepopt_addsub_c.pmg index 76c6bd74f..483fed4d1 100644 --- a/passes/opt/peepopt_addsub_c.pmg +++ b/passes/opt/peepopt_addsub_c.pmg @@ -1,7 +1,7 @@ pattern addsub_c // // Authored by Akash Levy of Silimate, Inc. under ISC license. -// Transforms add->add/sub_c into add_c canceling the constants out: +// Transforms add->add/sub_c into add_c folding the constants: // y = (a +- b_const) +- c_const ===> a +- eval(b_const +- c_const) // @@ -76,7 +76,7 @@ code if (GetSize(c_const) > 32) reject; - // Reuse/create new cell to drive the rewritten equation + // Reuse/create new cell to drive the folded expression if (nusers(addsub1_y) != 2) { cell = module->addCell(NEW_ID2_SUFFIX("asconst"), addsub1->type); cell->attributes = addsub1->attributes; diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys index 9a6a397cb..d8e09cc1e 100644 --- a/tests/peepopt/addsub_c.ys +++ b/tests/peepopt/addsub_c.ys @@ -178,7 +178,7 @@ log -push read_verilog < Date: Thu, 11 Sep 2025 04:50:58 -0700 Subject: [PATCH 7/8] Smallfixes --- tests/peepopt/addsub_c.ys | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys index d8e09cc1e..6a3888542 100644 --- a/tests/peepopt/addsub_c.ys +++ b/tests/peepopt/addsub_c.ys @@ -393,8 +393,8 @@ log -push read_verilog < Date: Thu, 11 Sep 2025 05:02:58 -0700 Subject: [PATCH 8/8] Smallfixes --- passes/opt/peepopt_addsub_c.pmg | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/passes/opt/peepopt_addsub_c.pmg b/passes/opt/peepopt_addsub_c.pmg index 483fed4d1..9002d55b7 100644 --- a/passes/opt/peepopt_addsub_c.pmg +++ b/passes/opt/peepopt_addsub_c.pmg @@ -36,11 +36,10 @@ match addsub2 endmatch code - // New addsub1 cell (will be reused unless there is external fanout) + // New addsub1 cell (will be reused unless there is external fanout) auto cell = addsub1; // Get addsub2 signals - SigSpec addsub2_a = port(addsub2, \A); SigSpec c_const = port(addsub2, \B); SigSpec addsub2_y = port(addsub2, \Y); @@ -69,7 +68,13 @@ code // Integer values should be lesser than 32 bits // This is because we are using C++ types, and int is 32 bits // FIXME: use long long or BigInteger to make pass work with >32 bits - if (GetSize(addsub1->getParam(ID::B_WIDTH)) > 32) + if (addsub1->getParam(ID::A_WIDTH).as_int() > 32) + reject; + if (addsub1->getParam(ID::B_WIDTH).as_int() > 32) + reject; + if (addsub2->getParam(ID::A_WIDTH).as_int() > 32) + reject; + if (addsub2->getParam(ID::B_WIDTH).as_int() > 32) reject; if (GetSize(b_const) > 32) reject;