This commit is contained in:
Geza Lore 2026-07-14 02:37:40 +00:00 committed by GitHub
commit e315a45c9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 158 additions and 41 deletions

View File

@ -135,6 +135,23 @@ struct VlIsVlWide : public std::false_type {};
template <std::size_t N_Words>
struct VlIsVlWide<VlWide<N_Words>> : public std::true_type {};
// Proxy class to get a WDataInP or WDataOutP from a VlWide4AB for 'A' or 'B' bits.
// This only exists transiently and is immediately converted to a WDataInP or WDataOutP
// via an implicit constructor.
enum class VlWide4ABPart { A_BITS, B_BITS };
template <VlWide4ABPart Part>
class VlWide4ABToWData final {
template <std::size_t N_Words>
friend struct VlWide4AB;
friend class WDataOutP;
friend class WDataInP;
EData* const m_datap;
explicit VlWide4ABToWData(EData* datap) VL_PURE : m_datap{datap} {}
explicit VlWide4ABToWData(const EData* datap) VL_PURE : m_datap{const_cast<EData*>(datap)} {}
};
// Opaque handles to backing store of a VlWide, These are used to pass the
// backing store to runtime functions without having to template all the
// runtime functions on the VlWide template patameters. They created via
@ -147,10 +164,17 @@ struct VlIsVlWide<VlWide<N_Words>> : public std::true_type {};
// Read-Write VlWide handle
class WDataOutP final {
EData* m_datap; // The backing store - non const as the handle is assignable
char* m_datap; // The backing store - non const as the handle is assignable
EData* basep() const VL_PURE {
return reinterpret_cast<EData*>(reinterpret_cast<uintptr_t>(m_datap) & ~0x1ULL);
}
size_t scale() const VL_PURE { //
return reinterpret_cast<uintptr_t>(m_datap) & 0x1ULL;
}
// Use WDataOutP::external() to create a handle from a raw pointer
explicit WDataOutP(EData* datap) VL_PURE : m_datap{datap} {}
explicit WDataOutP(EData* datap) VL_PURE : m_datap{reinterpret_cast<char*>(datap)} {}
public:
// FACTORY METHODS
@ -161,26 +185,44 @@ public:
// Implicit conversion from 'VlWide'
template <std::size_t N_Words>
// cppcheck-suppress noExplicitConstructor
/* implicit */ WDataOutP(VlWide<N_Words>& vlWide) VL_PURE : m_datap{vlWide.data()} {}
WDataOutP(const WDataOutP& other) VL_PURE = default;
WDataOutP(WDataOutP&& other) VL_PURE = default;
WDataOutP& operator=(const WDataOutP& other) VL_PURE = default;
/* implicit */ WDataOutP(VlWide<N_Words>& vlWide) VL_PURE
: m_datap{reinterpret_cast<char*>(vlWide.data())} {}
// Implicit conversion from 'VlWide4ABToWData'
template <VlWide4ABPart Part>
// cppcheck-suppress noExplicitConstructor
/* implicit */ WDataOutP(const VlWide4ABToWData<Part>& tmp) VL_PURE
: m_datap{reinterpret_cast<char*>(reinterpret_cast<uintptr_t>(tmp.m_datap) | 1)} {}
WDataOutP(const WDataOutP& other) = default;
WDataOutP(WDataOutP&& other) = default;
WDataOutP& operator=(const WDataOutP& other) = default;
// METHODS
EData* datap() const VL_PURE { return m_datap; }
operator bool() const VL_PURE { return m_datap; }
EData& operator[](size_t index) const VL_PURE { return m_datap[index]; }
WDataOutP operator+(size_t index) const VL_PURE { return WDataOutP(m_datap + index); }
WDataOutP operator+(int index) const VL_PURE { return WDataOutP(m_datap + index); }
EData* datap() const VL_PURE { return reinterpret_cast<EData*>(m_datap); }
operator bool() const VL_PURE { return basep(); }
EData& operator[](std::size_t index) const VL_PURE { return basep()[index << scale()]; }
WDataOutP operator+(std::size_t index) const VL_PURE {
return WDataOutP(reinterpret_cast<EData*>(m_datap + (index << scale()) * sizeof(EData)));
}
WDataOutP operator+(int index) const VL_PURE {
return WDataOutP(reinterpret_cast<EData*>(m_datap + (index << scale()) * sizeof(EData)));
}
};
static_assert(sizeof(WDataOutP) == sizeof(EData*), "WDataOutP should be a single pointer");
// Read-Only VlWide handle
class WDataInP final {
const EData* m_datap; // The backing store - non const as the handle is assignable
const char* m_datap; // The backing store - non const as the handle is assignable
const EData* basep() const VL_PURE {
return reinterpret_cast<const EData*>(reinterpret_cast<uintptr_t>(m_datap) & ~0x1ULL);
}
size_t scale() const VL_PURE { //
return reinterpret_cast<uintptr_t>(m_datap) & 0x1ULL;
}
// Use WDataInP::external() to create a handle from a raw pointer
explicit WDataInP(const EData* datap) VL_PURE : m_datap{datap} {}
explicit WDataInP(const EData* datap) VL_PURE : m_datap{reinterpret_cast<const char*>(datap)} {
}
public:
// FACTORY METHODS
@ -191,26 +233,40 @@ public:
// Implicit conversion from 'VlWide'
template <std::size_t N_Words>
// cppcheck-suppress noExplicitConstructor
/* implicit */ WDataInP(VlWide<N_Words>& vlWide) VL_PURE : m_datap{vlWide.data()} {}
/* implicit */ WDataInP(VlWide<N_Words>& vlWide) VL_PURE
: m_datap{reinterpret_cast<const char*>(vlWide.data())} {}
// Implicit conversion from 'const VlWide'
template <std::size_t N_Words>
// cppcheck-suppress noExplicitConstructor
/* implicit */ WDataInP(const VlWide<N_Words>& vlWide) VL_PURE : m_datap{vlWide.data()} {}
// Implicit conversion from 'WDataOutP'
/* implicit */ WDataInP(const VlWide<N_Words>& vlWide) VL_PURE
: m_datap{reinterpret_cast<const char*>(vlWide.data())} {}
// Implicit conversion from WDataOutP
// cppcheck-suppress noExplicitConstructor
/* implicit */ WDataInP(const WDataOutP& owp) VL_PURE : m_datap{owp.datap()} {}
// Initialize with 'nullptr'
explicit WDataInP(std::nullptr_t) VL_PURE : m_datap{nullptr} {}
WDataInP(const WDataInP& other) VL_PURE = default;
WDataInP(WDataInP&& other) VL_PURE = default;
WDataInP& operator=(const WDataInP& other) VL_PURE = default;
/* implicit */ WDataInP(const WDataOutP& owp) VL_PURE
: m_datap{reinterpret_cast<const char*>(owp.datap())} {}
explicit WDataInP(std::nullptr_t) // Initialize to nullptr
: m_datap{nullptr} {}
// Implicit conversion from VlWide4ABToWData
// cppcheck-suppress noExplicitConstructor
template <VlWide4ABPart Part>
/* implicit */ WDataInP(const VlWide4ABToWData<Part>& tmp) VL_PURE
: m_datap{reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(tmp.m_datap) | 1)} {}
WDataInP(const WDataInP& other) = default;
WDataInP(WDataInP&& other) = default;
WDataInP& operator=(const WDataInP& other) = default;
// METHODS
const EData* datap() const VL_PURE { return m_datap; }
operator bool() const VL_PURE { return m_datap; }
const EData& operator[](size_t index) const VL_PURE { return m_datap[index]; }
WDataInP operator+(size_t index) const VL_PURE { return WDataInP(m_datap + index); }
WDataInP operator+(int index) const VL_PURE { return WDataInP(m_datap + index); }
const EData* datap() const VL_PURE { return reinterpret_cast<const EData*>(m_datap); }
operator bool() const VL_PURE { return basep(); }
const EData& operator[](std::size_t index) const VL_PURE { return basep()[index << scale()]; }
WDataInP operator+(std::size_t index) const VL_PURE {
return WDataInP(
reinterpret_cast<const EData*>(m_datap + (index << scale()) * sizeof(EData)));
}
WDataInP operator+(int index) const VL_PURE {
return WDataInP(
reinterpret_cast<const EData*>(m_datap + (index << scale()) * sizeof(EData)));
}
};
static_assert(sizeof(WDataInP) == sizeof(EData*), "WDataInP should be a single pointer");
@ -221,6 +277,51 @@ bool VlWide<N_Words>::operator<(const VlWide<N_Words>& rhs) const VL_PURE {
return _vl_cmp_w(N_Words, *this, rhs) < 0;
}
// 4-state VlWide with 'A' and 'B' bits interleaved
template <std::size_t N_Words>
struct VlWide4AB final {
static constexpr size_t Words = N_Words;
using EData4AB = EData[2];
// MEMBERS
// This should be the only data member, otherwise generated static initializers need updating
EData4AB m_storage[N_Words]; // Contents of the packed array
// CONSTRUCTORS
// Default constructors and destructor are used. Note however that C++20 requires that
// aggregate types do not have a user declared constructor, not even an explicitly defaulted
// one.
// OPERATOR METHODS
// Default copy assignment operators are used.
bool operator==(const VlWide<N_Words>& that) const VL_PURE {
for (size_t i = 0; i < N_Words; ++i) {
if (m_storage[i][0] != that.m_storage[i][0]) return false;
if (m_storage[i][1] != that.m_storage[i][1]) return false;
}
return true;
}
bool operator!=(const VlWide<N_Words>& that) const VL_PURE { return !(*this == that); }
EData4AB& operator[](size_t index) { return m_storage[index]; }
const EData4AB& operator[](size_t index) const { return m_storage[index]; }
// METHODS
size_t size() const { return N_Words; }
VlWide4ABToWData<VlWide4ABPart::A_BITS> aBits() {
return VlWide4ABToWData<VlWide4ABPart::A_BITS>(&m_storage[0][0]);
}
VlWide4ABToWData<VlWide4ABPart::B_BITS> bBits() {
return VlWide4ABToWData<VlWide4ABPart::B_BITS>(&m_storage[0][1]);
}
VlWide4ABToWData<VlWide4ABPart::A_BITS> aBits() const {
return VlWide4ABToWData<VlWide4ABPart::A_BITS>(&m_storage[0][0]);
}
VlWide4ABToWData<VlWide4ABPart::B_BITS> bBits() const {
return VlWide4ABToWData<VlWide4ABPart::B_BITS>(&m_storage[0][1]);
}
};
//===================================================================
// String formatters (required by below containers)
@ -245,24 +346,24 @@ inline std::string VL_TO_STRING(const VlWide<N_Words>& obj) {
#define VL_SIG16(name, msb, lsb) SData name ///< Declare signal, 9-16 bits
#define VL_SIG64(name, msb, lsb) QData name ///< Declare signal, 33-64 bits
#define VL_SIG(name, msb, lsb) IData name ///< Declare signal, 17-32 bits
#define VL_SIGW(name, msb, lsb, words) VlWide<words> name ///< Declare signal, 65+ bits
#define VL_SIGW(name, msb, lsb, words) VlWide4AB<words> name ///< Declare signal, 65+ bits
#endif
#define VL_IN8(name, msb, lsb) CData name ///< Declare input signal, 1-8 bits
#define VL_IN16(name, msb, lsb) SData name ///< Declare input signal, 9-16 bits
#define VL_IN64(name, msb, lsb) QData name ///< Declare input signal, 33-64 bits
#define VL_IN(name, msb, lsb) IData name ///< Declare input signal, 17-32 bits
#define VL_INW(name, msb, lsb, words) VlWide<words> name ///< Declare input signal, 65+ bits
#define VL_INW(name, msb, lsb, words) VlWide4AB<words> name ///< Declare input signal, 65+ bits
#define VL_INOUT8(name, msb, lsb) CData name ///< Declare bidir signal, 1-8 bits
#define VL_INOUT16(name, msb, lsb) SData name ///< Declare bidir signal, 9-16 bits
#define VL_INOUT64(name, msb, lsb) QData name ///< Declare bidir signal, 33-64 bits
#define VL_INOUT(name, msb, lsb) IData name ///< Declare bidir signal, 17-32 bits
#define VL_INOUTW(name, msb, lsb, words) VlWide<words> name ///< Declare bidir signal, 65+ bits
#define VL_INOUTW(name, msb, lsb, words) VlWide4AB<words> name ///< Declare bidir signal, 65+ bits
#define VL_OUT8(name, msb, lsb) CData name ///< Declare output signal, 1-8 bits
#define VL_OUT16(name, msb, lsb) SData name ///< Declare output signal, 9-16 bits
#define VL_OUT64(name, msb, lsb) QData name ///< Declare output signal, 33-64 bits
#define VL_OUT(name, msb, lsb) IData name ///< Declare output signal, 17-32 bits
#define VL_OUTW(name, msb, lsb, words) VlWide<words> name ///< Declare output signal, 65+ bits
#define VL_OUTW(name, msb, lsb, words) VlWide4AB<words> name ///< Declare output signal, 65+ bits
//===================================================================
// Functions needed here

View File

@ -575,7 +575,7 @@ public:
if (width <= VL_IDATASIZE) return "IData";
if (width <= VL_QUADSIZE) return "QData";
return "VlWide<" + std::to_string(VL_WORDS_I(width)) + ">";
return "VlWide4AB<" + std::to_string(VL_WORDS_I(width)) + ">";
}
};
class AstClassRefDType final : public AstNodeDType {

View File

@ -1269,14 +1269,14 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
} else if (dtypep->isQuad()) {
info.m_type = "QData" + bitvec;
} else if (dtypep->isWide()) {
info.m_type = "VlWide<" + cvtToStr(dtypep->widthWords()) + ">" + bitvec;
info.m_type = "VlWide4AB<" + cvtToStr(dtypep->widthWords()) + ">" + bitvec;
}
// CData, SData, IData, QData or VlWide are packed type.
const bool packedType = VString::startsWith(info.m_type, "CData")
|| VString::startsWith(info.m_type, "SData")
|| VString::startsWith(info.m_type, "IData")
|| VString::startsWith(info.m_type, "QData")
|| VString::startsWith(info.m_type, "VlWide");
|| VString::startsWith(info.m_type, "VlWide4AB");
UASSERT_OBJ(!packed || packedType, this, "Unsupported type for packed struct or union");
} else {
v3fatalSrc("Unknown data type in var type emitter: " << dtypep->prettyName());

View File

@ -119,6 +119,7 @@ void EmitCFunc::emitOpName(AstNode* nodep, const string& format, AstNode* lhsp,
m_wideTempRefp->selfPointerProtect(m_useSelfForThis));
}
out += m_wideTempRefp->varp()->nameProtect();
out += ".bBits()";
m_wideTempRefp = nullptr;
needComma = true;
} else if (usesQueue) {
@ -623,6 +624,7 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
: "VL_SCOPED_RAND_RESET_W(");
out += cvtToStr(dtypep->widthMin());
out += ", " + varNameProtected + suffix;
out += ".bBits()";
if (!zeroit) {
emitVarResetScopeHash();
const uint64_t salt = VString::hashMurmur(varp->prettyName());

View File

@ -121,6 +121,8 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
bool m_createdScopeHash = false; // Already created a scope hash
bool m_noBits = false; // Don't emit .bBits() for wide variables
protected:
VL_DEFINE_DEBUG_FUNCTIONS;
@ -1782,6 +1784,7 @@ public:
emitDereference(nodep, nodep->selfPointerProtect(m_useSelfForThis));
}
putns(nodep, nodep->varp()->nameProtect());
if (nodep->isWide() && !m_noBits) puts(".bBits()");
}
void visit(AstAddrOfCFunc* nodep) override {
// Note: Can be thought to handle more, but this is all that is needed right now
@ -1805,12 +1808,8 @@ public:
puts(VSelfPointerText::replaceThis(m_useSelfForThis, "this"));
puts("}");
}
void visit(AstNodeSel* nodep) override {
if (!VN_IS(nodep, ArraySel) && !VN_IS(nodep, WordSel)) {
visit(static_cast<AstNodeBiop*>(nodep));
return;
}
// ArraySel or WordSel
void visit(AstNodeSel* nodep) override { visit(static_cast<AstNodeBiop*>(nodep)); }
void visit(AstArraySel* nodep) override {
iterateAndNextConstNull(nodep->fromp());
// Special case constant index for readability
if (AstConst* const idxp = VN_CAST(nodep->bitp(), Const)) {
@ -1821,6 +1820,21 @@ public:
iterateAndNextConstNull(nodep->bitp());
puts("]");
}
void visit(AstWordSel* nodep) override {
VL_RESTORER(m_noBits);
m_noBits = true;
iterateAndNextConstNull(nodep->fromp());
// Special case constant index for readability
if (AstConst* const idxp = VN_CAST(nodep->bitp(), Const)) {
puts("[" + std::to_string(idxp->toUInt()) + "U]");
puts("[1]"); // B bits
return;
}
putbs("[");
iterateAndNextConstNull(nodep->bitp());
puts("]");
puts("[1]"); // B bits
}
//
void visit(AstConsAssoc* nodep) override {

View File

@ -966,7 +966,7 @@ string V3Number::emitC() const VL_MT_STABLE {
// Note the double {{ initializer. The first { starts the initializer of the VlWide,
// and the second starts the initializer of m_storage within the VlWide.
// Alternative is to have constructor with std::initializer_list
result = "VlWide<" + std::to_string(words()) + ">{{";
result = "VlWide4AB<" + std::to_string(words()) + ">{{";
if (words() > 4) result += '\n';
for (int n = 0; n < words(); ++n) {
if (n) result += ((n % 4) ? ", " : ",\n");