yosys/passes/silimate/peepopt_manual2sub.pmg

239 lines
7.0 KiB
Plaintext

pattern manual2sub
//
// Authored by Abhinav Tondapu of Silimate, Inc. under ISC license.
//
// Canonicalize manual 2's complement subtraction:
// Case A: (a + ~b) + 1 ===> a - b
// Case B: a + (~b + 1) ===> a - b
//
// Note: Fanout checking includes module connections to avoid breaking
// designs where intermediate results are used by output assignments.
//
state <SigSpec> minuend subtrahend result_sig root_a root_b
state <bool> is_signed
state <SigSpec> inner_y inner_port_sig_A root_port_sig inner_port_sig_B
match root_add
select root_add->type == $add
set root_a port(root_add, \A)
set root_b port(root_add, \B)
set result_sig port(root_add, \Y)
set is_signed root_add->getParam(ID::A_SIGNED).as_bool()
endmatch
// Case A: (a + ~b) + 1
code root_add inner_y
{
SigSpec pa = root_a;
SigSpec pb = root_b;
auto is_one = [](SigSpec s) {
if (!s.is_fully_const()) return false;
Const c = s.as_const();
for (int i = 0; i < c.size(); i++) {
if (i == 0 && c[i] != State::S1) return false;
if (i > 0 && c[i] != State::S0) return false;
}
return true;
};
if (is_one(pa)) {
inner_y = pb;
} else if (is_one(pb)) {
inner_y = pa;
} else {
branch;
reject;
}
}
endcode
// Find the inner add whose Y feeds the non-constant port of root_add
match inner_add_A
select inner_add_A->type == $add
index <SigSpec> port(inner_add_A, \Y) === inner_y
filter nusers(port(inner_add_A, \Y)) == 2
endmatch
// Branch over both ports of inner_add_A to find the NOT gate
code inner_port_sig_A
inner_port_sig_A = port(inner_add_A, \A);
branch;
inner_port_sig_A = port(inner_add_A, \B);
endcode
match not_gate_A
select not_gate_A->type == $not
index <SigSpec> port(not_gate_A, \Y) === inner_port_sig_A
endmatch
code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed
{
// Require consistent signedness across the chain
if (root_add->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
if (inner_add_A->getParam(ID::A_SIGNED).as_bool() != is_signed)
reject;
if (inner_add_A->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
// Determine which port is the NOT output and which is the minuend
SigSpec not_y = port(not_gate_A, \Y);
SigSpec add_a = port(inner_add_A, \A);
SigSpec add_b = port(inner_add_A, \B);
if (not_y == add_a) {
minuend = inner_add_A->getPort(ID::B);
} else if (not_y == add_b) {
minuend = inner_add_A->getPort(ID::A);
} else {
reject;
}
subtrahend = port(not_gate_A, \A);
log(" creating $sub for %s from (a + ~b) + 1\n", log_signal(result_sig));
Cell *cell = root_add;
int width = GetSize(result_sig);
int inner_width = GetSize(inner_y);
// Reject if the +1 wrap boundary is narrower than the final result
if (inner_width < width)
reject;
// Anchor both operands to the inner add width to preserve carry behavior
SigSpec minuend_rs = minuend;
SigSpec subtrahend_rs = subtrahend;
minuend_rs.extend_u0(inner_width, is_signed);
subtrahend_rs.extend_u0(inner_width, is_signed);
SigSpec sub_y = result_sig;
if (inner_width != width) {
sub_y = module->addWire(NEW_ID2_SUFFIX("sub_y"), inner_width);
}
Cell *sub = module->addSub(NEW_ID2_SUFFIX("sub"), minuend_rs, subtrahend_rs, sub_y, is_signed);
// Extend the sub result back to root width when inner is wider
if (inner_width != width) {
SigSpec sub_y_rs = sub_y;
sub_y_rs.extend_u0(width, is_signed);
module->connect(result_sig, sub_y_rs);
}
sub->fixup_parameters();
autoremove(root_add);
autoremove(inner_add_A);
autoremove(not_gate_A);
did_something = true;
accept;
}
endcode
// Case B: a + (~b + 1)
// Branch over both ports of root_add to find the inner (~b + 1) add
code root_port_sig
root_port_sig = port(root_add, \A);
branch;
root_port_sig = port(root_add, \B);
endcode
match inner_add_B
select inner_add_B->type == $add
index <SigSpec> port(inner_add_B, \Y) === root_port_sig
filter nusers(port(inner_add_B, \Y)) == 2
endmatch
// Branch over both ports of inner_add_B to find the NOT gate
code inner_port_sig_B
inner_port_sig_B = port(inner_add_B, \A);
branch;
inner_port_sig_B = port(inner_add_B, \B);
endcode
match not_gate_B
select not_gate_B->type == $not
index <SigSpec> port(not_gate_B, \Y) === inner_port_sig_B
endmatch
code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed
{
// Require consistent signedness across the chain
if (root_add->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
if (inner_add_B->getParam(ID::A_SIGNED).as_bool() != is_signed)
reject;
if (inner_add_B->getParam(ID::B_SIGNED).as_bool() != is_signed)
reject;
// Verify inner_add_B has the form (~b + 1): one port is constant 1,
// the other is the NOT output
SigSpec pa = port(inner_add_B, \A);
SigSpec pb = port(inner_add_B, \B);
SigSpec not_y = port(not_gate_B, \Y);
auto is_one = [](SigSpec s) {
if (!s.is_fully_const()) return false;
Const c = s.as_const();
for (int i = 0; i < c.size(); i++) {
if (i == 0 && c[i] != State::S1) return false;
if (i > 0 && c[i] != State::S0) return false;
}
return true;
};
bool valid = false;
if (is_one(pa) && pb == not_y) valid = true;
if (is_one(pb) && pa == not_y) valid = true;
if (!valid) reject;
// The minuend is whichever root_add port is NOT the inner_add_B output
subtrahend = port(not_gate_B, \A);
if (port(inner_add_B, \Y) == port(root_add, \A))
minuend = root_b;
else
minuend = root_a;
log(" creating $sub for %s from a + (~b + 1)\n", log_signal(result_sig));
Cell *cell = root_add;
int width = GetSize(result_sig);
int inner_width = GetSize(inner_add_B->getPort(ID::Y));
// Reject if the +1 wrap boundary is narrower than the final result
if (inner_width < width)
reject;
// Anchor both operands to the inner add width to preserve carry behavior
SigSpec minuend_rs = minuend;
SigSpec subtrahend_rs = subtrahend;
minuend_rs.extend_u0(inner_width, is_signed);
subtrahend_rs.extend_u0(inner_width, is_signed);
SigSpec sub_y = result_sig;
if (inner_width != width) {
sub_y = module->addWire(NEW_ID2_SUFFIX("sub_y"), inner_width);
}
Cell *sub = module->addSub(NEW_ID2_SUFFIX("sub"), minuend_rs, subtrahend_rs, sub_y, is_signed);
// Extend the sub result back to root width when inner is wider
if (inner_width != width) {
SigSpec sub_y_rs = sub_y;
sub_y_rs.extend_u0(width, is_signed);
module->connect(result_sig, sub_y_rs);
}
sub->fixup_parameters();
autoremove(root_add);
autoremove(inner_add_B);
autoremove(not_gate_B);
did_something = true;
accept;
}
endcode