From 31a5197a1ce7f3b54d8a5a6d7cc94c07928f0265 Mon Sep 17 00:00:00 2001 From: Alain Dargelas Date: Wed, 15 Jan 2025 16:57:19 -0800 Subject: [PATCH] muxadd and muldiv_c peepopt --- Makefile | 1 + passes/pmgen/Makefile.inc | 2 + passes/pmgen/peepopt.cc | 27 ++- passes/pmgen/peepopt_muldiv_c.pmg | 64 ++++- passes/pmgen/peepopt_muxadd.pmg | 118 +++++++--- tests/peepopt/.gitignore | 1 + tests/peepopt/muldiv_c.ys | 343 +++++++++++++++++++++++++++ tests/peepopt/muxadd.ys | 378 ++++++++++++++++++++++++++++++ tests/peepopt/run-test.sh | 6 + 9 files changed, 902 insertions(+), 38 deletions(-) create mode 100644 tests/peepopt/.gitignore create mode 100644 tests/peepopt/muldiv_c.ys create mode 100644 tests/peepopt/muxadd.ys create mode 100755 tests/peepopt/run-test.sh diff --git a/Makefile b/Makefile index ea581300f..e0fc1c32a 100644 --- a/Makefile +++ b/Makefile @@ -918,6 +918,7 @@ SH_TEST_DIRS += tests/memlib SH_TEST_DIRS += tests/svinterfaces SH_TEST_DIRS += tests/xprop SH_TEST_DIRS += tests/select +SH_TEST_DIRS += tests/peepopt SH_TEST_DIRS += tests/proc SH_TEST_DIRS += tests/blif # SH_TEST_DIRS += tests/arch diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc index c2cd8b9e9..83f9b577a 100644 --- a/passes/pmgen/Makefile.inc +++ b/passes/pmgen/Makefile.inc @@ -59,6 +59,8 @@ PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftadd.pmg PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv_c.pmg PEEPOPT_PATTERN += passes/pmgen/peepopt_muxadd.pmg +PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv_c.pmg +PEEPOPT_PATTERN += passes/pmgen/peepopt_muxadd.pmg PEEPOPT_PATTERN += passes/pmgen/peepopt_formal_clockgateff.pmg passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) diff --git a/passes/pmgen/peepopt.cc b/passes/pmgen/peepopt.cc index e7a729ecb..94c4a6c14 100644 --- a/passes/pmgen/peepopt.cc +++ b/passes/pmgen/peepopt.cc @@ -28,6 +28,9 @@ bool did_something; // scratchpad configurations for pmgen int shiftadd_max_ratio; +// Helper function, removes LSB 0s +SigSpec remove_bottom_padding(SigSpec sig); + #include "passes/pmgen/peepopt_pm.h" struct PeepoptPass : public Pass { @@ -42,8 +45,6 @@ struct PeepoptPass : public Pass { log("\n"); log("This pass employs the following rules by default:\n"); log("\n"); - log(" * muxadd - Replace S?(A+B):A with A+(S?B:0)\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"); @@ -67,13 +68,17 @@ struct PeepoptPass : public Pass { log(" based pattern to prevent combinational paths from the\n"); log(" output to the enable input after running clk2fflogic.\n"); log("\n"); + log("If -withmuxadd is specified it adds the following rule:\n"); + log("\n"); + log(" * muxadd - Replace S?(A+B):A with A+(S?B:0)\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing PEEPOPT pass (run peephole optimizers).\n"); bool formalclk = false; - + bool withmuxadd = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -81,6 +86,10 @@ struct PeepoptPass : public Pass { formalclk = true; continue; } + if (args[argidx] == "-withmuxadd") { + withmuxadd = true; + continue; + } break; } extra_args(args, argidx, design); @@ -110,11 +119,21 @@ struct PeepoptPass : public Pass { pm.run_shiftmul_left(); pm.run_muldiv(); pm.run_muldiv_c(); - pm.run_muxadd(); + if (withmuxadd) + pm.run_muxadd(); } } } } } PeepoptPass; + +SigSpec remove_bottom_padding(SigSpec sig) +{ + int i = 0; + for (; i < sig.size() - 1 && sig[i] == State::S0; i++) { + } + return sig.extract(i, sig.size() - i); +} + PRIVATE_NAMESPACE_END diff --git a/passes/pmgen/peepopt_muldiv_c.pmg b/passes/pmgen/peepopt_muldiv_c.pmg index 7cf5f5266..2cf9b028b 100644 --- a/passes/pmgen/peepopt_muldiv_c.pmg +++ b/passes/pmgen/peepopt_muldiv_c.pmg @@ -1,7 +1,8 @@ pattern muldiv_c // -// Transforms mul->div into div->mul when b and c are divisible constants: -// y = (a * b_const) / c_const ===> a * (b_const / c_const) +// Authored by Akash Levy and Alain Dargelas of Silimate, Inc. under ISC license. +// Transforms mul->div into const->mul when b and c are divisible constants: +// y = (a * b_const) / c_const ===> a * eval(b_const / c_const) // state a b_const mul_y @@ -18,9 +19,8 @@ code a b_const mul_y mul_y = port(mul, \Y); // Fanout of each multiplier Y bit should be 1 (no bit-split) - for (auto bit : mul_y) - if (nusers(bit) != 2) - reject; + if (nusers(mul_y) != 2) + reject; // A and B can be interchanged branch; @@ -34,6 +34,7 @@ match div // Check that b_const and c_const is constant filter b_const.is_fully_const() filter port(div, \B).is_fully_const() + index remove_bottom_padding(port(div, \A)) === mul_y endmatch code @@ -46,13 +47,61 @@ code int offset = GetSize(div_a) - GetSize(mul_y); // Get properties and values of b_const and c_const - int b_const_width = mul->getParam(ID::B_WIDTH).as_int(); + // 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 = mul->getParam(ID::B_SIGNED).as_bool(); bool c_const_signed = div->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_ratio; + Const b_const_actual; + // Avoid division by zero + if (c_const_int == 0) + reject; + b_const_actual = b_const_int_shifted; + b_const_actual.compress(b_const_signed); + + const_ratio = b_const_int_shifted / c_const_int; + const_ratio.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(mul->getParam(ID::B_WIDTH)) > 32) + reject; + if (GetSize(b_const) > 32) + reject; + if (GetSize(c_const) + offset > 32) + reject; + + // Check for potential multiplier overflow + if (GetSize(b_const_actual) + GetSize(a) > GetSize(mul_y)) + reject; + // Check that there are only zeros before offset if (offset < 0 || !div_a.extract(0, offset).is_fully_zero()) reject; @@ -62,7 +111,8 @@ code reject; // Rewire to only keep multiplier - mul->setPort(\B, Const(b_const_int_shifted / c_const_int, b_const_width)); + mul->setPort(\A, a); + mul->setPort(\B, const_ratio); mul->setPort(\Y, div_y); // Remove divider diff --git a/passes/pmgen/peepopt_muxadd.pmg b/passes/pmgen/peepopt_muxadd.pmg index 0aff8ba64..c403ce75f 100644 --- a/passes/pmgen/peepopt_muxadd.pmg +++ b/passes/pmgen/peepopt_muxadd.pmg @@ -1,59 +1,123 @@ pattern muxadd // +// Authored by Akash Levy and Alain Dargelas of Silimate, Inc. under ISC license. // Transforms add->mux into mux->add: // y = s ? (a + b) : a ===> y = a + (s ? b : 0) -// +// or +// y = s ? a : (a + b) ===> y = a + (s ? 0 : b) -state add_a add_b add_y +state add_a add_b add_y add_a_ext mux_a mux_b mux_y +state add_a_signed +state add_a_id add_b_id mux_a_id mux_b_id match add // Select adder select add->type == $add + + // Set ports, allowing A and B to be swapped + choice A {\A, \B} + define B (A == \A ? \B : \A) + set add_a port(add, A) + set add_b port(add, B) + set add_y port(add, \Y) + + // Get signedness + set add_a_signed param(add, (A == \A) ? \A_SIGNED : \B_SIGNED) + + // Choice ids + set add_a_id A + set add_b_id B endmatch -code add_y add_a add_b +code add_y add_a add_b add_a_ext // Get adder signals - add_a = port(add, \A); - add_b = port(add, \B); - add_y = port(add, \Y); + add_a_ext = SigSpec(port(add, add_a_id)); + add_a_ext.extend_u0(GetSize(add_y), add_a_signed.as_bool()); // Fanout of each adder Y bit should be 1 (no bit-split) - for (auto bit : add_y) - if (nusers(bit) != 2) - reject; - - // A and B can be interchanged - branch; - std::swap(add_a, add_b); + if (nusers(add_y) != 2) + reject; endcode -match mux - // Select mux of form s ? (a + b) : a, allow leading 0s when A_WIDTH != Y_WIDTH +match mux + // Select mux of form: s ? (a + b) : a + // Allow leading 0s when A_WIDTH != Y_WIDTH or s ? a : (a + b) select mux->type == $mux - index port(mux, \A) === SigSpec({Const(State::S0, GetSize(add_y)-GetSize(add_a)), add_a}) - index port(mux, \B) === add_y + choice AB {\A, \B} + define BA (AB == \A ? \B : \A) + set mux_y port(mux, \Y) + set mux_a port(mux, AB) + set mux_b port(mux, BA) + set mux_a_id AB + set mux_b_id BA + index port(mux, AB) === add_a_ext + index port(mux, BA) === add_y endmatch -code +code add_y add_a add_b add_a_ext add_a_id add_b_id mux_y mux_a mux_b mux_a_id mux_b_id // Get mux signal - SigSpec mux_y = port(mux, \Y); + SigSpec mid; + std::string adder_y_name; + if (add_y.is_wire()) + adder_y_name = add_y.as_wire()->name.c_str(); + else + adder_y_name = add_y.as_string(); - // SILIMATE: Alias cell to mux for mid wire - Cell *cell = mux; + // Start by renaming the LHS of an eventual assign statement + // where the RHS is the adder output (that is getting rewired). + // Renaming the signal allows equiv_opt to function as it would + // otherwise try to match the functionality which would fail + // as the LHS signal has indeed changed function. + + // Adder output could be assigned + for (auto it = module->connections().begin(); it != module->connections().end(); ++it) { + RTLIL::SigSpec rhs = it->second; + if (rhs.is_wire()) { + const std::string& rhs_name = rhs.as_wire()->name.c_str(); + if (rhs_name == adder_y_name) { + RTLIL::SigSpec lhs = it->first; + if (lhs.is_wire()) { + const std::string& lhs_name = lhs.as_wire()->name.c_str(); + module->rename(lhs_name, module->uniquify("$" + lhs_name)); + break; + } + } + } + } + // Alternatively, the port name could be a wire name + if (add_y.is_wire()) { + if (GetSize(adder_y_name)) { + if (adder_y_name[0] != '$') { + module->rename(adder_y_name, module->uniquify("$" + adder_y_name)); + } + } + } else { + for (auto chunk : add_y.chunks()) { + if (chunk.is_wire()) { + const std::string& name = chunk.wire->name.c_str(); + if (name[0] != '$') { + module->rename(name, module->uniquify("$" + name)); + } + } + } + } // Create new mid wire - SigSpec mid = module->addWire(NEW_ID2_SUFFIX("mid"), GetSize(add_b)); // SILIMATE: Improve the naming + mid = module->addWire(NEW_ID, GetSize(add_b)); - // Rewire - mux->setPort(\A, Const(State::S0, GetSize(add_b))); - mux->setPort(\B, add_b); + // Connect ports + add->setPort(add_b_id, mid); + add->setPort(add_a_id, add_a); + add->setPort(\Y, add_y); + mux->setPort(mux_a_id, Const(State::S0, GetSize(add_b))); + mux->setPort(mux_b_id, add_b); mux->setPort(\Y, mid); - add->setPort(\B, mid); - add->setPort(\Y, mux_y); + module->connect(mux_y, add_y); // Log, fixup, accept log("muxadd pattern in %s: mux=%s, add=%s\n", log_id(module), log_id(mux), log_id(add)); add->fixup_parameters(); mux->fixup_parameters(); + did_something = true; accept; endcode diff --git a/tests/peepopt/.gitignore b/tests/peepopt/.gitignore new file mode 100644 index 000000000..50e13221d --- /dev/null +++ b/tests/peepopt/.gitignore @@ -0,0 +1 @@ +/*.log diff --git a/tests/peepopt/muldiv_c.ys b/tests/peepopt/muldiv_c.ys new file mode 100644 index 000000000..62777caab --- /dev/null +++ b/tests/peepopt/muldiv_c.ys @@ -0,0 +1,343 @@ +log -header "Test simple positive case" +log -push +design -reset +read_verilog < (a+b) > a" +log -push +design -reset +read_verilog < a > b" +log -push +design -reset +read_verilog < b > a" +log -push +design -reset +read_verilog <