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..9002d55b7 --- /dev/null +++ b/passes/opt/peepopt_addsub_c.pmg @@ -0,0 +1,101 @@ +pattern addsub_c +// +// Authored by Akash Levy of Silimate, Inc. under ISC license. +// Transforms add->add/sub_c into add_c folding the constants: +// 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); + + // 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 port(addsub2, \A) === addsub1_y +endmatch + +code + // New addsub1 cell (will be reused unless there is external fanout) + auto cell = addsub1; + + // Get addsub2 signals + SigSpec c_const = port(addsub2, \B); + SigSpec addsub2_y = port(addsub2, \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); + + // Calculate the constant and compress the width to fit the value + Const const_value; + if (addsub1->type == $add) + if (addsub2->type == $add) + const_value = b_const_int + c_const_int; + else + const_value = b_const_int - c_const_int; + else + if (addsub2->type == $add) + const_value = b_const_int - c_const_int; + else + 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 + // 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 (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; + if (GetSize(c_const) > 32) + reject; + + // 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; + 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)); + cell->fixup_parameters(); + accept; +endcode diff --git a/tests/peepopt/addsub_c.ys b/tests/peepopt/addsub_c.ys new file mode 100644 index 000000000..6a3888542 --- /dev/null +++ b/tests/peepopt/addsub_c.ys @@ -0,0 +1,953 @@ +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 +select t:$add -assert-count 0 +select t:$sub -assert-count 1 +design -reset +log -pop