Fix for GitHub issue 9 part 2 : Efficiency of the verinum pow() function.

This changes the verinum pow() function to use the more efficient algorithm
used in the vvp runtime. It will still be slow if the left operand is unsized
and the right operand is large, as it will expand the result vector to avoid
overflow.
This commit is contained in:
Martin Whitaker 2014-02-15 22:06:31 +00:00
parent 5853f7d867
commit 66bdbb77ec
2 changed files with 76 additions and 32 deletions

View File

@ -788,7 +788,7 @@ unsigned PEBLeftWidth::test_width(Design*des, NetScope*scope, width_mode_t&mode)
case 'p': // ** case 'p': // **
if (lc && rc) { if (lc && rc) {
verinum result = pow(lc->value(), rc->value()); verinum result = pow(lc->value(), rc->value());
use_width = result.len(); use_width = max(use_width, result.len());
} else { } else {
if (signed_flag_) use_width -= 1; if (signed_flag_) use_width -= 1;
use_width *= (unsigned)r_val; use_width *= (unsigned)r_val;

View File

@ -344,9 +344,11 @@ verinum::~verinum()
verinum& verinum::operator= (const verinum&that) verinum& verinum::operator= (const verinum&that)
{ {
if (this == &that) return *this; if (this == &that) return *this;
if (nbits_ != that.nbits_) {
delete[]bits_; delete[]bits_;
nbits_ = that.nbits_; nbits_ = that.nbits_;
bits_ = new V[that.nbits_]; bits_ = new V[that.nbits_];
}
for (unsigned idx = 0 ; idx < nbits_ ; idx += 1) for (unsigned idx = 0 ; idx < nbits_ ; idx += 1)
bits_[idx] = that.bits_[idx]; bits_[idx] = that.bits_[idx];
@ -1126,53 +1128,95 @@ verinum operator * (const verinum&left, const verinum&right)
return trim_vnum(result); return trim_vnum(result);
} }
static verinum make_p_one(unsigned len, bool has_len, bool has_sign)
{
verinum tmp (verinum::V0, has_len ? len : 2, has_len);
tmp.set(0, verinum::V1);
tmp.has_sign(has_sign);
return tmp;
}
static verinum make_m_one(unsigned len, bool has_len, bool has_sign)
{
verinum tmp (verinum::V1, has_len ? len : 1, has_len);
tmp.has_sign(has_sign);
return tmp;
}
static verinum recursive_pow(const verinum&left, verinum&right)
{
// If the exponent is zero, return a value of 1
if (right.is_zero()) {
return make_p_one(left.len(), left.has_len(), left.has_sign());
}
verinum res;
if (right.get(0) == 1) {
// The exponent is odd, so subtract 1 from it and recurse
right.set(0, verinum::V0);
res = pow(left, right);
res = left * res;
} else {
// The exponent is even, so divide it by 2 and recurse
right = right >> 1;
res = pow(left, right);
res = res * res;
}
if (left.has_len()) {
res = verinum(res, left.len());
}
return res;
}
verinum pow(const verinum&left, const verinum&right) verinum pow(const verinum&left, const verinum&right)
{ {
verinum result = left; verinum result;
long pow_count = right.as_long();
// We need positive and negative one in a few places. // We need positive and negative one in a few places.
unsigned len = left.len(); verinum p_one = make_p_one(left.len(), left.has_len(), left.has_sign());
// Positive one must be at least two bits wide! verinum m_one = make_m_one(left.len(), left.has_len(), left.has_sign());
verinum one (verinum::V0, (len<2) ? 2 : len, left.has_len());
one.has_sign(left.has_sign());
one.set(0, verinum::V1);
verinum m_one (verinum::V1, len, left.has_len());
m_one.has_sign(true);
// If either the right or left values are undefined we return 'bx. // If either the right or left values are undefined we return 'bx.
if (!right.is_defined() || !left.is_defined()) { if (!right.is_defined() || !left.is_defined()) {
result = verinum(verinum::Vx, len, left.has_len()); result = verinum(verinum::Vx, left.len(), left.has_len());
result.has_sign(left.has_sign()); result.has_sign(left.has_sign());
// If the right value is zero we need to set the result to 1. // If the right value is zero we need to set the result to 1.
} else if (pow_count == 0) { } else if (right.is_zero()) {
result = one; result = p_one;
} else if (pow_count < 0) {
} else if (right.is_negative()) {
// 0 ** <negative> is 'bx. // 0 ** <negative> is 'bx.
if (left.is_zero()) { if (left.is_zero()) {
result = verinum(verinum::Vx, len, left.has_len()); result = verinum(verinum::Vx, left.len(), left.has_len());
result.has_sign(left.has_sign()); result.has_sign(left.has_sign());
// 1 ** <negative> is 1.
} else if (left == one) { // -1 ** <negative> is 1 or -1. Note that this must be done
result = one; // before testing for +1 in case 'left' has a width of 1.
// -1 ** <negative> is 1 or -1.
} else if (left.has_sign() && left == m_one) { } else if (left.has_sign() && left == m_one) {
if (pow_count%2 == 0) { if (right.get(0) == verinum::V0) {
result = one; result = p_one;
} else { } else {
result = m_one; result = m_one;
} }
// 1 ** <negative> is 1.
} else if (left == p_one) {
result = p_one;
// Everything else is 0. // Everything else is 0.
} else { } else {
result = verinum(verinum::V0, len, left.has_len()); result = verinum(verinum::V0, left.len(), left.has_len());
result.has_sign(left.has_sign()); result.has_sign(left.has_sign());
} }
} else {
verinum exponent = right;
result = recursive_pow(left, exponent);
} }
for (long idx = 1 ; idx < pow_count ; idx += 1) return trim_vnum(result);
result = result * left;
return result;
} }
verinum operator << (const verinum&that, unsigned shift) verinum operator << (const verinum&that, unsigned shift)