mirror of https://github.com/YosysHQ/yosys.git
239 lines
7.0 KiB
Plaintext
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
|