diff --git a/include/verilated_types.h b/include/verilated_types.h index a48b99c4c..08b3fe178 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -135,6 +135,23 @@ struct VlIsVlWide : public std::false_type {}; template struct VlIsVlWide> : 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 +class VlWide4ABToWData final { + template + 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(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> : 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(reinterpret_cast(m_datap) & ~0x1ULL); + } + size_t scale() const VL_PURE { // + return reinterpret_cast(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(datap)} {} public: // FACTORY METHODS @@ -161,26 +185,44 @@ public: // Implicit conversion from 'VlWide' template // cppcheck-suppress noExplicitConstructor - /* implicit */ WDataOutP(VlWide& 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& vlWide) VL_PURE + : m_datap{reinterpret_cast(vlWide.data())} {} + // Implicit conversion from 'VlWide4ABToWData' + template + // cppcheck-suppress noExplicitConstructor + /* implicit */ WDataOutP(const VlWide4ABToWData& tmp) VL_PURE + : m_datap{reinterpret_cast(reinterpret_cast(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(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(m_datap + (index << scale()) * sizeof(EData))); + } + WDataOutP operator+(int index) const VL_PURE { + return WDataOutP(reinterpret_cast(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(reinterpret_cast(m_datap) & ~0x1ULL); + } + size_t scale() const VL_PURE { // + return reinterpret_cast(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(datap)} { + } public: // FACTORY METHODS @@ -191,26 +233,40 @@ public: // Implicit conversion from 'VlWide' template // cppcheck-suppress noExplicitConstructor - /* implicit */ WDataInP(VlWide& vlWide) VL_PURE : m_datap{vlWide.data()} {} + /* implicit */ WDataInP(VlWide& vlWide) VL_PURE + : m_datap{reinterpret_cast(vlWide.data())} {} // Implicit conversion from 'const VlWide' template // cppcheck-suppress noExplicitConstructor - /* implicit */ WDataInP(const VlWide& vlWide) VL_PURE : m_datap{vlWide.data()} {} - // Implicit conversion from 'WDataOutP' + /* implicit */ WDataInP(const VlWide& vlWide) VL_PURE + : m_datap{reinterpret_cast(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(owp.datap())} {} + explicit WDataInP(std::nullptr_t) // Initialize to nullptr + : m_datap{nullptr} {} + // Implicit conversion from VlWide4ABToWData + // cppcheck-suppress noExplicitConstructor + template + /* implicit */ WDataInP(const VlWide4ABToWData& tmp) VL_PURE + : m_datap{reinterpret_cast(reinterpret_cast(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(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(m_datap + (index << scale()) * sizeof(EData))); + } + WDataInP operator+(int index) const VL_PURE { + return WDataInP( + reinterpret_cast(m_datap + (index << scale()) * sizeof(EData))); + } }; static_assert(sizeof(WDataInP) == sizeof(EData*), "WDataInP should be a single pointer"); @@ -221,6 +277,51 @@ bool VlWide::operator<(const VlWide& rhs) const VL_PURE { return _vl_cmp_w(N_Words, *this, rhs) < 0; } +// 4-state VlWide with 'A' and 'B' bits interleaved +template +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& 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& 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 aBits() { + return VlWide4ABToWData(&m_storage[0][0]); + } + VlWide4ABToWData bBits() { + return VlWide4ABToWData(&m_storage[0][1]); + } + VlWide4ABToWData aBits() const { + return VlWide4ABToWData(&m_storage[0][0]); + } + VlWide4ABToWData bBits() const { + return VlWide4ABToWData(&m_storage[0][1]); + } +}; + //=================================================================== // String formatters (required by below containers) @@ -245,24 +346,24 @@ inline std::string VL_TO_STRING(const VlWide& 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 name ///< Declare signal, 65+ bits +#define VL_SIGW(name, msb, lsb, words) VlWide4AB 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 name ///< Declare input signal, 65+ bits +#define VL_INW(name, msb, lsb, words) VlWide4AB 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 name ///< Declare bidir signal, 65+ bits +#define VL_INOUTW(name, msb, lsb, words) VlWide4AB 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 name ///< Declare output signal, 65+ bits +#define VL_OUTW(name, msb, lsb, words) VlWide4AB name ///< Declare output signal, 65+ bits //=================================================================== // Functions needed here diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index fcb2183ba..539802f1c 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -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 { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 9b94d4e89..06a15655f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -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()); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 11f2ac14a..28cc3b3a4 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -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()); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 5e527870c..1cffedae4 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -121,6 +121,8 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit { std::unordered_map 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(nodep)); - return; - } - // ArraySel or WordSel + void visit(AstNodeSel* nodep) override { visit(static_cast(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 { diff --git a/src/V3Number.cpp b/src/V3Number.cpp index d97e4ed50..bcd9fe386 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -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");