From 96e6bf02c4854741e25db6302ff7e47090c695b7 Mon Sep 17 00:00:00 2001 From: CatalpaEel <139088727+CatalpaEel@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:12:30 +0800 Subject: [PATCH] Optimize wide four-state constant operations in V3Number (#8333) --- docs/CONTRIBUTORS | 1 + src/V3Number.cpp | 188 ++++++++++++++++++++++++++++++---------------- src/V3Number.h | 2 + 3 files changed, 126 insertions(+), 65 deletions(-) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 5f28a1c68..b217e3b43 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -46,6 +46,7 @@ BRDR LIFE Brian Li Cameron Kirk Cameron Waite +CatalpaEel Chih-Mao Chen Chris Bachhuber Chris Randall diff --git a/src/V3Number.cpp b/src/V3Number.cpp index db5093237..096609363 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -530,7 +530,8 @@ void V3Number::setBitX0(int bit) { V3Number& V3Number::setMask(int nbits, int lsb) { setZero(); - for (int bit = lsb; bit < lsb + nbits; ++bit) setBit(bit, 1); + UASSERT(lsb >= 0, "Negative destination bit range"); + fillBits(lsb, std::max(0, std::min(nbits, width() - lsb)), 1); return *this; } @@ -1153,8 +1154,10 @@ uint8_t V3Number::dataByte(int byte) const { bool V3Number::isAllZ() const VL_MT_SAFE { if (isDouble() || isString()) return false; - for (int i = 0; i < width(); ++i) { - if (!bitIsZ(i)) return false; + for (int i = 0; i < words(); ++i) { + const ValueAndX v = m_data.num()[i]; + const uint32_t mask = i == words() - 1 ? hiWordMask() : ~0U; + if (((~v.m_value & v.m_valueX) & mask) != mask) return false; } return true; } @@ -1215,16 +1218,24 @@ bool V3Number::isFourState() const VL_MT_SAFE { } bool V3Number::isAnyX() const VL_MT_SAFE { if (isDouble() || isString()) return false; - for (int bit = 0; bit < width(); ++bit) { - if (bitIsX(bit)) return true; + for (int i = 0; i < words(); ++i) { + const ValueAndX v = m_data.num()[i]; + if (v.m_value & v.m_valueX) return true; + } + return false; +} +bool V3Number::isAnyXZ() const { + if (isDouble() || isString()) return false; + for (int i = 0; i < words(); ++i) { + if (m_data.num()[i].m_valueX) return true; } return false; } -bool V3Number::isAnyXZ() const { return isAnyX() || isAnyZ(); } bool V3Number::isAnyZ() const VL_MT_SAFE { if (isDouble() || isString()) return false; - for (int bit = 0; bit < width(); ++bit) { - if (bitIsZ(bit)) return true; + for (int i = 0; i < words(); ++i) { + const ValueAndX v = m_data.num()[i]; + if (~v.m_value & v.m_valueX) return true; } return false; } @@ -1565,6 +1576,57 @@ V3Number& V3Number::opXor(const V3Number& lhs, const V3Number& rhs) { return *this; } +void V3Number::fillBits(int destLsb, int width, char value) { + UASSERT(destLsb >= 0 && width >= 0, "Negative bit range"); + if (!width) return; + UASSERT(width <= this->width() - destLsb, "Destination bit range exceeds number width"); + + const uint32_t valueWord = value == '1' || value == 'x' || value == 1 || value == 3 ? ~0U : 0; + const uint32_t xWord = value == 'z' || value == 'x' || value == 2 || value == 3 ? ~0U : 0; + const int firstWord = VL_BITWORD_E(destLsb); + const int lastWord = VL_BITWORD_E(destLsb + width - 1); + for (int word = firstWord; word <= lastWord; ++word) { + const int wordLsb = word == firstWord ? VL_BITBIT_E(destLsb) : 0; + const int wordMsb = word == lastWord ? VL_BITBIT_E(destLsb + width - 1) : VL_SIZEBITS_E; + const uint32_t mask = VL_MASK_E(wordMsb - wordLsb + 1) << wordLsb; + ValueAndX& dest = m_data.num()[word]; + dest.m_value = (dest.m_value & ~mask) | (valueWord & mask); + dest.m_valueX = (dest.m_valueX & ~mask) | (xWord & mask); + } +} + +void V3Number::copyBits(int destLsb, const V3Number& source, int sourceLsb, int width) { + UASSERT(destLsb >= 0 && sourceLsb >= 0 && width >= 0, "Negative bit range"); + if (!width) return; + UASSERT(width <= this->width() - destLsb, "Destination bit range exceeds number width"); + UASSERT(width <= source.width() - sourceLsb, "Source bit range exceeds number width"); + + const int firstWord = VL_BITWORD_E(destLsb); + const int lastWord = VL_BITWORD_E(destLsb + width - 1); + for (int destWord = firstWord; destWord <= lastWord; ++destWord) { + const int destBit = std::max(destLsb, destWord * VL_EDATASIZE); + const int destOffset = VL_BITBIT_E(destBit); + const int sourceBit = sourceLsb + destBit - destLsb; + const int sourceWord = VL_BITWORD_E(sourceBit); + const int sourceOffset = VL_BITBIT_E(sourceBit); + const int copyWidth = std::min(width - (destBit - destLsb), VL_EDATASIZE - destOffset); + const uint32_t mask = VL_MASK_E(copyWidth) << destOffset; + + const ValueAndX& sourceLo = source.m_data.num()[sourceWord]; + uint32_t value = sourceLo.m_value >> sourceOffset; + uint32_t valueX = sourceLo.m_valueX >> sourceOffset; + if (sourceOffset && copyWidth > VL_EDATASIZE - sourceOffset) { + const ValueAndX& sourceHi = source.m_data.num()[sourceWord + 1]; + value |= sourceHi.m_value << (VL_EDATASIZE - sourceOffset); + valueX |= sourceHi.m_valueX << (VL_EDATASIZE - sourceOffset); + } + + ValueAndX& dest = m_data.num()[destWord]; + dest.m_value = (dest.m_value & ~mask) | ((value << destOffset) & mask); + dest.m_valueX = (dest.m_valueX & ~mask) | ((valueX << destOffset) & mask); + } +} + V3Number& V3Number::opConcat(const V3Number& lhs, const V3Number& rhs) { // Correct number of zero bits/width matters NUM_ASSERT_OP_ARGS2(lhs, rhs); @@ -1574,15 +1636,8 @@ V3Number& V3Number::opConcat(const V3Number& lhs, const V3Number& rhs) { if (!lhs.sized() || !rhs.sized()) { v3warn(WIDTHCONCAT, "Unsized numbers/parameters not allowed in concatenations."); } - int obit = 0; - for (int bit = 0; bit < rhs.width(); ++bit) { - setBit(obit, rhs.bitIs(bit)); - ++obit; - } - for (int bit = 0; bit < lhs.width(); ++bit) { - setBit(obit, lhs.bitIs(bit)); - ++obit; - } + copyBits(0, rhs, 0, rhs.width()); + copyBits(rhs.width(), lhs, 0, lhs.width()); return *this; } @@ -1616,12 +1671,11 @@ V3Number& V3Number::opRepl(const V3Number& lhs, << v3Global.opt.replicationLimit() << " is suspect: " << rhsval); } setZero(); - int obit = 0; for (unsigned times = 0; times < rhsval; ++times) { - for (int bit = 0; bit < lhs.width(); ++bit) { - setBit(obit, lhs.bitIs(bit)); - ++obit; - } + const uint64_t destLsb = uint64_t{times} * lhs.width(); + if (destLsb >= static_cast(width())) break; + copyBits(static_cast(destLsb), lhs, 0, + std::min(lhs.width(), width() - static_cast(destLsb))); } return *this; } @@ -1638,9 +1692,7 @@ V3Number& V3Number::opStreamL(const V3Number& lhs, const V3Number& rhs) { const int ssize = std::min(rhs.toUInt(), static_cast(lhs.width())); for (int istart = 0; istart < lhs.width(); istart += ssize) { const int ostart = std::max(0, lhs.width() - ssize - istart); - for (int bit = 0; bit < ssize && bit < lhs.width() - istart; ++bit) { - setBit(ostart + bit, lhs.bitIs(istart + bit)); - } + copyBits(ostart, lhs, istart, std::min(ssize, lhs.width() - istart)); } return *this; } @@ -1974,12 +2026,11 @@ V3Number& V3Number::opShiftR(const V3Number& lhs, const V3Number& rhs) { NUM_ASSERT_LOGIC_ARGS2(lhs, rhs); if (rhs.isFourState()) return setAllBitsX(); setZero(); - for (int bit = 32; bit < rhs.width(); ++bit) { - if (rhs.bitIs1(bit)) return *this; // shift of over 2^32 must be zero - } + if (rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32)) return *this; const uint32_t rhsval = rhs.toUInt(); if (rhsval < static_cast(lhs.width())) { - for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIs(bit + rhsval)); + copyBits(0, lhs, static_cast(rhsval), + std::min(width(), lhs.width() - static_cast(rhsval))); } return *this; } @@ -1996,14 +2047,14 @@ V3Number& V3Number::opShiftRS(const V3Number& lhs, const V3Number& rhs, uint32_t const bool overflow = rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32); if (!overflow) { const uint32_t rhsval = rhs.toUInt(); - if (rhsval < static_cast(lhs.width())) { - for (int bit = 0; bit < width(); ++bit) { - setBit(bit, lhs.bitIsExtend(bit + rhsval, lbits)); - } + if (rhsval < lbits) { + const int copyWidth = std::min(width(), static_cast(lbits - rhsval)); + copyBits(0, lhs, static_cast(rhsval), copyWidth); + fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1)); return *this; } } - for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIs(lbits - 1)); // '0/'1/'x/'z + fillBits(0, width(), lhs.bitIs(lbits - 1)); // '0/'1/'x/'z return *this; } @@ -2013,12 +2064,11 @@ V3Number& V3Number::opShiftL(const V3Number& lhs, const V3Number& rhs) { NUM_ASSERT_LOGIC_ARGS2(lhs, rhs); if (rhs.isFourState()) return setAllBitsX(); setZero(); - for (int bit = 32; bit < rhs.width(); ++bit) { - if (rhs.bitIs1(bit)) return *this; // shift of over 2^32 must be zero - } + if (rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32)) return *this; const uint32_t rhsval = rhs.toUInt(); - for (uint32_t bit = 0; bit < static_cast(width()); ++bit) { - if (bit >= rhsval) setBit(bit, lhs.bitIs(bit - rhsval)); + if (rhsval < static_cast(width())) { + copyBits(static_cast(rhsval), lhs, 0, + std::min(width() - static_cast(rhsval), lhs.width())); } return *this; } @@ -2387,8 +2437,11 @@ V3Number& V3Number::opBufIf1(const V3Number& ens, const V3Number& if1s) { // Sets all bits in range to the given value V3Number& V3Number::opSetRange(uint32_t lsb, uint32_t width, char bitValue) { - const uint32_t msb = lsb + width - 1; - for (uint32_t i = lsb; i <= msb; ++i) setBit(i, bitValue); + if (lsb < static_cast(this->width())) { + fillBits(static_cast(lsb), + static_cast(std::min(width, static_cast(this->width()) - lsb)), + bitValue); + } return *this; } @@ -2413,10 +2466,17 @@ V3Number& V3Number::opAssignNonXZ(const V3Number& lhs, bool ignoreXZ) { setZero(); } else if (lhs.isDouble()) { setDouble(lhs.toDouble()); + } else if (!ignoreXZ) { + setZero(); + copyBits(0, lhs, 0, std::min(width(), lhs.width())); } else { - for (int bit = 0; bit < this->width(); ++bit) { - setBit(bit, ignoreXZ ? lhs.bitIs1(bit) : lhs.bitIs(bit)); + for (int word = 0; word < words(); ++word) { + const uint32_t value = word < lhs.words() ? lhs.m_data.num()[word].m_value + & ~lhs.m_data.num()[word].m_valueX + : 0; + m_data.num()[word] = {value, 0}; } + opCleanThis(); } } return *this; @@ -2443,10 +2503,9 @@ V3Number& V3Number::opExtendS(const V3Number& lhs, uint32_t lbits) { NUM_ASSERT_OP_ARGS1(lhs); NUM_ASSERT_LOGIC_ARGS1(lhs); setZero(); - for (int bit = 0; bit < width(); ++bit) { - const char extendWith = lhs.bitIsExtend(bit, lbits); - setBit(bit, extendWith); - } + const int copyWidth = std::min(width(), static_cast(lbits)); + copyBits(0, lhs, 0, copyWidth); + fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1)); return *this; } @@ -2455,7 +2514,9 @@ V3Number& V3Number::opExtendXZ(const V3Number& lhs, uint32_t lbits) { NUM_ASSERT_OP_ARGS1(lhs); NUM_ASSERT_LOGIC_ARGS1(lhs); setZero(); - for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIsExtend(bit, lbits)); + const int copyWidth = std::min(width(), static_cast(lbits)); + copyBits(0, lhs, 0, copyWidth); + fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1)); return *this; } @@ -2487,15 +2548,15 @@ V3Number& V3Number::opSel(const V3Number& lhs, uint32_t msbval, uint32_t lsbval) NUM_ASSERT_OP_ARGS1(lhs); NUM_ASSERT_LOGIC_ARGS1(lhs); setZero(); - int ibit = lsbval; - for (int bit = 0; bit < width(); ++bit) { - if (ibit >= 0 && ibit < lhs.width() && ibit <= static_cast(msbval)) { - setBit(bit, lhs.bitIs(ibit)); - } else { - setBitX0(bit); - } - ++ibit; + int copyWidth = 0; + if (lsbval <= msbval && lsbval < static_cast(lhs.width())) { + const uint64_t selectedWidth = uint64_t{msbval} - lsbval + 1; + copyWidth = static_cast( + std::min({static_cast(width()), static_cast(lhs.width()) - lsbval, + selectedWidth})); } + copyBits(0, lhs, copyWidth ? static_cast(lsbval) : 0, copyWidth); + fillBits(copyWidth, width() - copyWidth, v3Global.constRemoveXs() ? 0 : 'x'); // UINFO(0, "RANGE " << lhs << " " << msb << " " << lsb << " = " << *this); return *this; } @@ -2508,15 +2569,12 @@ V3Number& V3Number::opSelInto(const V3Number& lhs, int lsbval, int width) { // this[lsbval+width-1 : lsbval] = lhs; Other bits of this are not affected NUM_ASSERT_OP_ARGS1(lhs); NUM_ASSERT_LOGIC_ARGS1(lhs); - int ibit = 0; - for (int bit = lsbval; bit < lsbval + width; ++bit) { - if (ibit >= 0 && ibit < lhs.width()) { - setBit(bit, lhs.bitIs(ibit)); - } else { - setBitX0(bit); - } - ++ibit; - } + UASSERT(lsbval >= 0, "Negative destination bit range"); + const int copyWidth = std::max(0, std::min({width, lhs.width(), this->width() - lsbval})); + copyBits(lsbval, lhs, 0, copyWidth); + const int fillWidth + = std::max(0, std::min(width - copyWidth, this->width() - lsbval - copyWidth)); + fillBits(lsbval + copyWidth, fillWidth, v3Global.constRemoveXs() ? 0 : 'x'); return *this; } diff --git a/src/V3Number.h b/src/V3Number.h index 6568f27ff..404add9e7 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -397,6 +397,8 @@ class V3Number final { m_data.m_autoExtend = true; } V3Number& setSingleBits(char value); + void fillBits(int destLsb, int width, char value); + void copyBits(int destLsb, const V3Number& source, int sourceLsb, int width); V3Number& setString(const string& str) { m_data.setString(str); return *this;