opt_expr: Fix mul/div/mod by POT patterns to support >= 32 bits.

The previous code, in addition to being needlessly limitted to 32 bits
in the first place, also had UB for the 31th bit (doing 1 << 31).
This commit is contained in:
Marcelina Kościelnicka 2021-06-09 18:41:57 +02:00
parent 12b3a9765d
commit 1667ad658b
3 changed files with 122 additions and 124 deletions

View File

@ -363,6 +363,26 @@ bool RTLIL::Const::is_fully_undef() const
return true; return true;
} }
bool RTLIL::Const::is_onehot(int *pos) const
{
cover("kernel.rtlil.const.is_onehot");
bool found = false;
for (int i = 0; i < GetSize(*this); i++) {
auto &bit = bits[i];
if (bit != RTLIL::State::S0 && bit != RTLIL::State::S1)
return false;
if (bit == RTLIL::State::S1) {
if (found)
return false;
if (pos)
*pos = i;
found = true;
}
}
return found;
}
bool RTLIL::AttrObject::has_attribute(RTLIL::IdString id) const bool RTLIL::AttrObject::has_attribute(RTLIL::IdString id) const
{ {
return attributes.count(id); return attributes.count(id);
@ -4211,6 +4231,19 @@ bool RTLIL::SigSpec::has_marked_bits() const
return false; return false;
} }
bool RTLIL::SigSpec::is_onehot(int *pos) const
{
cover("kernel.rtlil.sigspec.is_onehot");
pack();
if (!is_fully_const())
return false;
log_assert(GetSize(chunks_) <= 1);
if (width_)
return RTLIL::Const(chunks_[0].data).is_onehot(pos);
return false;
}
bool RTLIL::SigSpec::as_bool() const bool RTLIL::SigSpec::as_bool() const
{ {
cover("kernel.rtlil.sigspec.as_bool"); cover("kernel.rtlil.sigspec.as_bool");

View File

@ -662,6 +662,7 @@ struct RTLIL::Const
bool is_fully_ones() const; bool is_fully_ones() const;
bool is_fully_def() const; bool is_fully_def() const;
bool is_fully_undef() const; bool is_fully_undef() const;
bool is_onehot(int *pos = nullptr) const;
inline RTLIL::Const extract(int offset, int len = 1, RTLIL::State padding = RTLIL::State::S0) const { inline RTLIL::Const extract(int offset, int len = 1, RTLIL::State padding = RTLIL::State::S0) const {
RTLIL::Const ret; RTLIL::Const ret;
@ -934,6 +935,7 @@ public:
bool is_fully_undef() const; bool is_fully_undef() const;
bool has_const() const; bool has_const() const;
bool has_marked_bits() const; bool has_marked_bits() const;
bool is_onehot(int *pos = nullptr) const;
bool as_bool() const; bool as_bool() const;
int as_int(bool is_signed = false) const; int as_int(bool is_signed = false) const;

View File

@ -393,29 +393,6 @@ int get_highest_hot_index(RTLIL::SigSpec signal)
return -1; return -1;
} }
// if the signal has only one bit set, return the index of that bit.
// otherwise return -1
int get_onehot_bit_index(RTLIL::SigSpec signal)
{
int bit_index = -1;
for (int i = 0; i < GetSize(signal); i++)
{
if (signal[i] == RTLIL::State::S0)
continue;
if (signal[i] != RTLIL::State::S1)
return -1;
if (bit_index != -1)
return -1;
bit_index = i;
}
return bit_index;
}
void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool consume_x, bool mux_undef, bool mux_bool, bool do_fine, bool keepdc, bool noclkinv) void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool consume_x, bool mux_undef, bool mux_bool, bool do_fine, bool keepdc, bool noclkinv)
{ {
if (!design->selected(module)) if (!design->selected(module))
@ -1526,14 +1503,12 @@ skip_identity:
RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B));
RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y));
if (sig_b.is_fully_const() && sig_b.size() <= 32) if (sig_b.is_fully_const())
std::swap(sig_a, sig_b), std::swap(a_signed, b_signed), swapped_ab = true; std::swap(sig_a, sig_b), std::swap(a_signed, b_signed), swapped_ab = true;
if (sig_a.is_fully_def() && sig_a.size() <= 32) if (sig_a.is_fully_def())
{ {
int a_val = sig_a.as_int(); if (sig_a.is_fully_zero())
if (a_val == 0)
{ {
cover("opt.opt_expr.mul_shift.zero"); cover("opt.opt_expr.mul_shift.zero");
@ -1547,16 +1522,16 @@ skip_identity:
goto next_cell; goto next_cell;
} }
for (int i = 1; i < (a_signed ? sig_a.size()-1 : sig_a.size()); i++) int exp;
if (a_val == (1 << i)) if (sig_a.is_onehot(&exp) && !(a_signed && exp == GetSize(sig_a) - 1))
{ {
if (swapped_ab) if (swapped_ab)
cover("opt.opt_expr.mul_shift.swapped"); cover("opt.opt_expr.mul_shift.swapped");
else else
cover("opt.opt_expr.mul_shift.unswapped"); cover("opt.opt_expr.mul_shift.unswapped");
log_debug("Replacing multiply-by-%d cell `%s' in module `%s' with shift-by-%d.\n", log_debug("Replacing multiply-by-%s cell `%s' in module `%s' with shift-by-%d.\n",
a_val, cell->name.c_str(), module->name.c_str(), i); log_signal(sig_a), cell->name.c_str(), module->name.c_str(), exp);
if (!swapped_ab) { if (!swapped_ab) {
cell->setPort(ID::A, cell->getPort(ID::B)); cell->setPort(ID::A, cell->getPort(ID::B));
@ -1564,10 +1539,7 @@ skip_identity:
cell->parameters.at(ID::A_SIGNED) = cell->parameters.at(ID::B_SIGNED); cell->parameters.at(ID::A_SIGNED) = cell->parameters.at(ID::B_SIGNED);
} }
std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(i, 6); Const new_b = exp;
while (GetSize(new_b) > 1 && new_b.back() == RTLIL::State::S0)
new_b.pop_back();
cell->type = ID($shl); cell->type = ID($shl);
cell->parameters[ID::B_WIDTH] = GetSize(new_b); cell->parameters[ID::B_WIDTH] = GetSize(new_b);
@ -1622,7 +1594,7 @@ skip_identity:
} }
} }
if (!keepdc && cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor)))
{ {
bool a_signed = cell->parameters[ID::A_SIGNED].as_bool(); bool a_signed = cell->parameters[ID::A_SIGNED].as_bool();
bool b_signed = cell->parameters[ID::B_SIGNED].as_bool(); bool b_signed = cell->parameters[ID::B_SIGNED].as_bool();
@ -1630,11 +1602,9 @@ skip_identity:
SigSpec sig_b = assign_map(cell->getPort(ID::B)); SigSpec sig_b = assign_map(cell->getPort(ID::B));
SigSpec sig_y = assign_map(cell->getPort(ID::Y)); SigSpec sig_y = assign_map(cell->getPort(ID::Y));
if (sig_b.is_fully_def() && sig_b.size() <= 32) if (sig_b.is_fully_def())
{ {
int b_val = sig_b.as_int(); if (sig_b.is_fully_zero())
if (b_val == 0)
{ {
cover("opt.opt_expr.divmod_zero"); cover("opt.opt_expr.divmod_zero");
@ -1648,22 +1618,19 @@ skip_identity:
goto next_cell; goto next_cell;
} }
for (int i = 0; i < (b_signed ? sig_b.size()-1 : sig_b.size()); i++) int exp;
if (b_val == (1 << i)) if (!keepdc && sig_b.is_onehot(&exp) && !(b_signed && exp == GetSize(sig_b) - 1))
{ {
if (cell->type.in(ID($div), ID($divfloor))) if (cell->type.in(ID($div), ID($divfloor)))
{ {
cover("opt.opt_expr.div_shift"); cover("opt.opt_expr.div_shift");
bool is_truncating = cell->type == ID($div); bool is_truncating = cell->type == ID($div);
log_debug("Replacing %s-divide-by-%d cell `%s' in module `%s' with shift-by-%d.\n", log_debug("Replacing %s-divide-by-%s cell `%s' in module `%s' with shift-by-%d.\n",
is_truncating ? "truncating" : "flooring", is_truncating ? "truncating" : "flooring",
b_val, cell->name.c_str(), module->name.c_str(), i); log_signal(sig_b), cell->name.c_str(), module->name.c_str(), exp);
std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(i, 6); Const new_b = exp;
while (GetSize(new_b) > 1 && new_b.back() == RTLIL::State::S0)
new_b.pop_back();
cell->type = ID($sshr); cell->type = ID($sshr);
cell->parameters[ID::B_WIDTH] = GetSize(new_b); cell->parameters[ID::B_WIDTH] = GetSize(new_b);
@ -1672,16 +1639,13 @@ skip_identity:
// Truncating division is the same as flooring division, except when // Truncating division is the same as flooring division, except when
// the result is negative and there is a remainder - then trunc = floor + 1 // the result is negative and there is a remainder - then trunc = floor + 1
if (is_truncating && a_signed && i != 0) { if (is_truncating && a_signed && GetSize(sig_a) != 0 && exp != 0) {
Wire *flooring = module->addWire(NEW_ID, sig_y.size()); Wire *flooring = module->addWire(NEW_ID, sig_y.size());
cell->setPort(ID::Y, flooring); cell->setPort(ID::Y, flooring);
Wire *result_neg = module->addWire(NEW_ID); SigSpec a_sign = sig_a[sig_a.size()-1];
module->addXor(NEW_ID, sig_a[sig_a.size()-1], sig_b[sig_b.size()-1], result_neg); SigSpec rem_nonzero = module->ReduceOr(NEW_ID, sig_a.extract(0, exp));
Wire *rem_nonzero = module->addWire(NEW_ID); SigSpec should_add = module->And(NEW_ID, a_sign, rem_nonzero);
module->addReduceOr(NEW_ID, sig_a.extract(0, i), rem_nonzero);
Wire *should_add = module->addWire(NEW_ID);
module->addAnd(NEW_ID, result_neg, rem_nonzero, should_add);
module->addAdd(NEW_ID, flooring, should_add, sig_y); module->addAdd(NEW_ID, flooring, should_add, sig_y);
} }
@ -1692,38 +1656,37 @@ skip_identity:
cover("opt.opt_expr.mod_mask"); cover("opt.opt_expr.mod_mask");
bool is_truncating = cell->type == ID($mod); bool is_truncating = cell->type == ID($mod);
log_debug("Replacing %s-modulo-by-%d cell `%s' in module `%s' with bitmask.\n", log_debug("Replacing %s-modulo-by-%s cell `%s' in module `%s' with bitmask.\n",
is_truncating ? "truncating" : "flooring", is_truncating ? "truncating" : "flooring",
b_val, cell->name.c_str(), module->name.c_str()); log_signal(sig_b), cell->name.c_str(), module->name.c_str());
std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(State::S1, i); // truncating modulo has the same masked bits as flooring modulo, but
// the sign bits are those of A (except when R=0)
if (is_truncating && a_signed && GetSize(sig_a) != 0 && exp != 0)
{
module->remove(cell);
SigSpec truncating = sig_a.extract(0, exp);
if (b_signed || i == 0) SigSpec a_sign = sig_a[sig_a.size()-1];
SigSpec rem_nonzero = module->ReduceOr(NEW_ID, sig_a.extract(0, exp));
SigSpec extend_bit = module->And(NEW_ID, a_sign, rem_nonzero);
truncating.append(extend_bit);
module->addPos(NEW_ID, truncating, sig_y, true);
}
else
{
std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(State::S1, exp);
if (b_signed || exp == 0)
new_b.push_back(State::S0); new_b.push_back(State::S0);
cell->type = ID($and); cell->type = ID($and);
cell->parameters[ID::B_WIDTH] = GetSize(new_b); cell->parameters[ID::B_WIDTH] = GetSize(new_b);
cell->setPort(ID::B, new_b); cell->setPort(ID::B, new_b);
// truncating modulo has the same masked bits as flooring modulo, but
// the sign bits are those of A (except when R=0)
if (is_truncating && a_signed && i != 0) {
Wire *flooring = module->addWire(NEW_ID, sig_y.size());
cell->setPort(ID::Y, flooring);
SigSpec truncating = SigSpec(flooring).extract(0, i);
Wire *rem_nonzero = module->addWire(NEW_ID);
module->addReduceOr(NEW_ID, truncating, rem_nonzero);
SigSpec a_sign = sig_a[sig_a.size()-1];
Wire *extend_bit = module->addWire(NEW_ID);
module->addAnd(NEW_ID, a_sign, rem_nonzero, extend_bit);
truncating.append(extend_bit);
module->addPos(NEW_ID, truncating, sig_y, true);
}
cell->check(); cell->check();
} }
}
did_something = true; did_something = true;
goto next_cell; goto next_cell;
@ -1957,8 +1920,8 @@ skip_alu_split:
replace = true; replace = true;
} }
int const_bit_hot = get_onehot_bit_index(const_sig); int const_bit_hot;
if (const_bit_hot >= 0 && const_bit_hot < var_width) if (const_sig.is_onehot(&const_bit_hot) && const_bit_hot < var_width)
{ {
RTLIL::SigSpec var_high_sig(RTLIL::State::S0, var_width - const_bit_hot); RTLIL::SigSpec var_high_sig(RTLIL::State::S0, var_width - const_bit_hot);
for (int i = const_bit_hot; i < var_width; i++) { for (int i = const_bit_hot; i < var_width; i++) {