3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-24 01:25:33 +00:00

muxadd and muldiv_c peepopt

This commit is contained in:
Alain Dargelas 2025-01-15 16:57:19 -08:00
parent 8dabfbe429
commit 31a5197a1c
9 changed files with 902 additions and 38 deletions

View file

@ -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)

View file

@ -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<std::string> 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

View file

@ -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 <SigSpec> 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 <SigSpec> 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

View file

@ -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 <SigSpec> add_a add_b add_y
state <SigSpec> add_a add_b add_y add_a_ext mux_a mux_b mux_y
state <Const> add_a_signed
state <IdString> 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 <IdString> A {\A, \B}
define <IdString> 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 <SigSpec> port(mux, \A) === SigSpec({Const(State::S0, GetSize(add_y)-GetSize(add_a)), add_a})
index <SigSpec> port(mux, \B) === add_y
choice <IdString> AB {\A, \B}
define <IdString> 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 <SigSpec> port(mux, AB) === add_a_ext
index <SigSpec> 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