From 5f1dc73a1b3df5d47ad5c66f9422e3c5ed3bd39d Mon Sep 17 00:00:00 2001 From: Kefa Chen <65221037+ckf104@users.noreply.github.com> Date: Sun, 3 Mar 2024 23:23:04 +0800 Subject: [PATCH] Support public packed struct / union (#860) (#4878) --- docs/CONTRIBUTORS | 1 + include/verilated_funcs.h | 111 ++++++++++ src/V3AstNodeDType.h | 20 +- src/V3AstNodes.cpp | 51 +++-- src/V3Class.cpp | 45 +++- src/V3EmitCHeaders.cpp | 142 +++++++++++- src/V3Name.cpp | 42 +++- src/V3Number.cpp | 5 + src/V3Number.h | 1 + src/V3Randomize.cpp | 3 +- src/V3UniqueNames.h | 20 +- test_regress/t/t_export_packed_struct.cpp | 245 +++++++++++++++++++++ test_regress/t/t_export_packed_struct.pl | 24 ++ test_regress/t/t_export_packed_struct.v | 133 +++++++++++ test_regress/t/t_export_packed_struct2.cpp | 122 ++++++++++ test_regress/t/t_export_packed_struct2.pl | 24 ++ test_regress/t/t_export_packed_struct2.v | 69 ++++++ 17 files changed, 1015 insertions(+), 43 deletions(-) create mode 100644 test_regress/t/t_export_packed_struct.cpp create mode 100755 test_regress/t/t_export_packed_struct.pl create mode 100644 test_regress/t/t_export_packed_struct.v create mode 100644 test_regress/t/t_export_packed_struct2.cpp create mode 100755 test_regress/t/t_export_packed_struct2.pl create mode 100644 test_regress/t/t_export_packed_struct2.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 89010f63c..fa22f84fd 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -103,6 +103,7 @@ Justin Thiel Kaleb Barrett Kamil Rakoczy Kanad Kanhere +Kefa Chen Keith Colbert Kevin Kiningham Kritik Bhimani diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index b53cd2ab8..f03a59ea8 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -1309,6 +1309,26 @@ static inline void _vl_insert_WI(WDataOutP iowp, IData ld, int hbit, int lbit, } } +// Copy bits from lwp[hbit:lbit] to low bits of lhsr. rbits is real width of lshr +static inline void _vl_insert_IW(IData& lhsr, WDataInP const lwp, int hbit, int lbit, + int rbits = 0) VL_MT_SAFE { + const int hoffset = VL_BITBIT_E(hbit); + const int loffset = VL_BITBIT_E(lbit); + const int hword = VL_BITWORD_E(hbit); + const int lword = VL_BITWORD_E(lbit); + const IData cleanmask = VL_MASK_I(rbits); + if (hword == lword) { + const IData insmask = (VL_MASK_I(hoffset - loffset + 1)); + lhsr = (lhsr & ~insmask) | ((lwp[lword] >> loffset) & (insmask & cleanmask)); + } else { + const int nbitsonright = VL_IDATASIZE - loffset; // bits that filled by lword + const IData hinsmask = (VL_MASK_E(hoffset - 0 + 1)) << nbitsonright; + const IData linsmask = VL_MASK_E(VL_EDATASIZE - loffset); + lhsr = (lhsr & ~linsmask) | ((lwp[lword] >> loffset) & (linsmask & cleanmask)); + lhsr = (lhsr & ~hinsmask) | ((lwp[hword] << nbitsonright) & (hinsmask & cleanmask)); + } +} + // INTERNAL: Stuff large LHS bit 0++ into OUTPUT at specified offset // lwp may be "dirty" static inline void _vl_insert_WW(WDataOutP iowp, WDataInP const lwp, int hbit, int lbit, @@ -2083,6 +2103,97 @@ static inline void VL_ASSIGNSEL_WW(int rbits, int obits, int lsb, WDataOutP iowp _vl_insert_WW(iowp, rwp, lsb + obits - 1, lsb, rbits); } +//==================================================== +// Range assignments + +// These additional functions copy bits range [obis+roffset-1:roffset] from rhs to lower bits +// of lhs(select before assigning). Rhs should always be wider than lhs. +static inline void VL_SELASSIGN_II(int rbits, int obits, CData& lhsr, IData rhs, + int roffset) VL_PURE { + _vl_insert_II(lhsr, rhs >> roffset, obits - 1, 0, rbits); +} +static inline void VL_SELASSIGN_II(int rbits, int obits, SData& lhsr, IData rhs, + int roffset) VL_PURE { + _vl_insert_II(lhsr, rhs >> roffset, obits - 1, 0, rbits); +} +static inline void VL_SELASSIGN_II(int rbits, int obits, IData& lhsr, IData rhs, + int roffset) VL_PURE { + _vl_insert_II(lhsr, rhs >> roffset, obits - 1, 0, rbits); +} +static inline void VL_SELASSIGN_IQ(int rbits, int obits, CData& lhsr, QData rhs, + int roffset) VL_PURE { + // it will be truncated to right CData mask + const CData cleanmask = VL_MASK_I(rbits); + const CData insmask = VL_MASK_I(obits); + lhsr = (lhsr & ~insmask) | (static_cast(rhs >> roffset) & (insmask & cleanmask)); +} +static inline void VL_SELASSIGN_IQ(int rbits, int obits, SData& lhsr, QData rhs, + int roffset) VL_PURE { + // it will be truncated to right CData mask + const SData cleanmask = VL_MASK_I(rbits); + const SData insmask = VL_MASK_I(obits); + lhsr = (lhsr & ~insmask) | (static_cast(rhs >> roffset) & (insmask & cleanmask)); +} +static inline void VL_SELASSIGN_IQ(int rbits, int obits, IData& lhsr, QData rhs, + int roffset) VL_PURE { + const IData cleanmask = VL_MASK_I(rbits); + const IData insmask = VL_MASK_I(obits); + lhsr = (lhsr & ~insmask) | (static_cast(rhs >> roffset) & (insmask & cleanmask)); +} + +static inline void VL_SELASSIGN_QQ(int rbits, int obits, QData& lhsr, QData rhs, + int roffset) VL_PURE { + _vl_insert_QQ(lhsr, rhs >> roffset, obits - 1, 0, rbits); +} + +static inline void VL_SELASSIGN_IW(int rbits, int obits, CData& lhsr, WDataInP const rhs, + int roffset) VL_MT_SAFE { + IData l = static_cast(lhsr); + _vl_insert_IW(l, rhs, roffset + obits - 1, roffset, rbits); + lhsr = static_cast(l); +} +static inline void VL_SELASSIGN_IW(int rbits, int obits, SData& lhsr, WDataInP const rhs, + int roffset) VL_MT_SAFE { + IData l = static_cast(lhsr); + _vl_insert_IW(l, rhs, roffset + obits - 1, roffset, rbits); + lhsr = static_cast(l); +} +static inline void VL_SELASSIGN_IW(int rbits, int obits, IData& lhsr, WDataInP const rhs, + int roffset) VL_MT_SAFE { + _vl_insert_IW(lhsr, rhs, roffset + obits - 1, roffset, rbits); +} +static inline void VL_SELASSIGN_QW(int rbits, int obits, QData& lhsr, WDataInP const rhs, + int roffset) VL_MT_SAFE { + // assert VL_QDATASIZE >= rbits > VL_IDATASIZE; + IData low = static_cast(lhsr); + IData high = static_cast(lhsr >> VL_IDATASIZE); + if (obits <= VL_IDATASIZE) { + _vl_insert_IW(low, rhs, obits + roffset - 1, roffset, VL_IDATASIZE); + } else { + _vl_insert_IW(low, rhs, roffset + VL_IDATASIZE - 1, roffset, VL_IDATASIZE); + _vl_insert_IW(high, rhs, roffset + obits - 1, roffset + VL_IDATASIZE, + rbits - VL_IDATASIZE); + } + lhsr = (static_cast(high) << VL_IDATASIZE) | low; +} + +static inline void VL_SELASSIGN_WW(int rbits, int obits, WDataOutP iowp, WDataInP const rwp, + int roffset) VL_MT_SAFE { + // assert rbits > VL_QDATASIZE + const int wordoff = roffset / VL_EDATASIZE; + const int lsb = roffset & VL_SIZEBITS_E; + const int upperbits = lsb == 0 ? 0 : VL_EDATASIZE - lsb; + // If roffset is not aligned, we copy some bits to align it. + if (lsb != 0) { + const int w = obits < upperbits ? obits : upperbits; + const int insmask = VL_MASK_E(w); + iowp[0] = (iowp[0] & ~insmask) | ((rwp[wordoff] >> lsb) & insmask); + if (w == obits) return; + obits -= w; + } + _vl_insert_WW(iowp, rwp + wordoff + (lsb != 0), upperbits + obits - 1, upperbits, rbits); +} + //====================================================================== // Triops diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index b6457f9f3..b857dd58b 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -126,13 +126,13 @@ public: const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isQuad() ? "Q" : "I"); } - string cType(const string& name, bool forFunc, bool isRef) const; + string cType(const string& name, bool forFunc, bool isRef, bool packed=false) const; // Represents a C++ LiteralType? (can be constexpr) bool isLiteralType() const VL_MT_STABLE; private: class CTypeRecursed; - CTypeRecursed cTypeRecurse(bool compound) const; + CTypeRecursed cTypeRecurse(bool compound, bool packed) const; }; class AstNodeArrayDType VL_NOT_FINAL : public AstNodeDType { // Array data type, ie "some_dtype var_name [2:0]" @@ -510,11 +510,17 @@ public: int widthAlignBytes() const override { return 8; } // Assume int widthTotalBytes() const override { return 8; } // Assume bool isCompound() const override { return true; } - static string typeToHold(uint64_t maxItem) { - return (maxItem < (1ULL << 8)) ? "CData" - : (maxItem < (1ULL << 16)) ? "SData" - : (maxItem < (1ULL << 32)) ? "IData" - : "QData"; + static string typeToHold(int width) { + if (width <= 8) + return "CData"; + else if (width <= 16) + return "SData"; + else if (width <= VL_IDATASIZE) + return "IData"; + else if (width <= VL_QUADSIZE) + return "QData"; + else + return "VlWide<" + std::to_string(VL_WORDS_I(width)) + ">"; } }; class AstClassRefDType final : public AstNodeDType { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 5c269915f..8e0df4bf9 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -738,49 +738,66 @@ public: } }; -string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const { - const CTypeRecursed info = cTypeRecurse(false); +string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef, bool packed) const { + const CTypeRecursed info = cTypeRecurse(false, packed); return info.render(name, isRef); } -AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { +AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packed) const { // Legacy compound argument currently just passed through and unused CTypeRecursed info; const AstNodeDType* const dtypep = this->skipRefp(); if (const auto* const adtypep = VN_CAST(dtypep, AssocArrayDType)) { - const CTypeRecursed key = adtypep->keyDTypep()->cTypeRecurse(true); - const CTypeRecursed val = adtypep->subDTypep()->cTypeRecurse(true); + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + const CTypeRecursed key = adtypep->keyDTypep()->cTypeRecurse(true, false); + const CTypeRecursed val = adtypep->subDTypep()->cTypeRecurse(true, false); info.m_type = "VlAssocArray<" + key.m_type + ", " + val.m_type + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, CDType)) { + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); info.m_type = adtypep->name(); } else if (const auto* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { - const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true, false); info.m_type = "VlAssocArray"; } else if (const auto* const adtypep = VN_CAST(dtypep, DynArrayDType)) { - const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true, false); info.m_type = "VlQueue<" + sub.m_type + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, QueueDType)) { - const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true, false); info.m_type = "VlQueue<" + sub.m_type; // + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1 if (adtypep->boundp()) info.m_type += ", " + cvtToStr(adtypep->boundConst() + 1); info.m_type += ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, SampleQueueDType)) { - const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true, false); info.m_type = "VlSampleQueue<" + sub.m_type + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) { + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); info.m_type = "VlClassRef<" + EmitCBase::prefixNameProtect(adtypep) + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) { + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); info.m_type = EmitCBase::prefixNameProtect(adtypep->ifaceViaCellp()) + "*"; } else if (const auto* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); if (adtypep->isCompound()) compound = true; - const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(compound); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(compound, false); info.m_type = "VlUnpacked<" + sub.m_type; info.m_type += ", " + cvtToStr(adtypep->declRange().elements()); info.m_type += ">"; - } else if (VN_IS(dtypep, NodeUOrStructDType) && !VN_AS(dtypep, NodeUOrStructDType)->packed()) { - const auto* const sdtypep = VN_AS(dtypep, NodeUOrStructDType); + } else if (packed && (VN_IS(dtypep, PackArrayDType))) { + const AstPackArrayDType* const adtypep = VN_CAST(dtypep, PackArrayDType); + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(false, true); + info.m_type = std::move(sub.m_type); + info.m_dims = "[" + cvtToStr(adtypep->elementsConst()) + "]" + sub.m_dims; + } else if (VN_IS(dtypep, NodeUOrStructDType) + && (!VN_AS(dtypep, NodeUOrStructDType)->packed() || packed)) { + const AstNodeUOrStructDType* const sdtypep = VN_AS(dtypep, NodeUOrStructDType); + UASSERT_OBJ(!packed || sdtypep->packed(), this, + "Unsupported type for packed struct or union"); info.m_type = EmitCBase::prefixNameProtect(sdtypep); } else if (const AstBasicDType* const bdtypep = dtypep->basicp()) { // We don't print msb()/lsb() as multidim packed would require recursion, @@ -823,6 +840,13 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { } else if (dtypep->isWide()) { info.m_type = "VlWide<" + 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"); + UASSERT_OBJ(!packed || packedType, this, "Unsupported type for packed struct or union"); } else { v3fatalSrc("Unknown data type in var type emitter: " << dtypep->prettyName()); } @@ -1728,7 +1752,8 @@ AstNodeUOrStructDType* AstMemberDType::getChildStructp() const { while (AstNodeArrayDType* const asubdtp = VN_CAST(subdtp, NodeArrayDType)) { subdtp = asubdtp->subDTypep(); } - return VN_CAST(subdtp, NodeUOrStructDType); // Maybe nullptr + // It's possible that `subdtp` is still a ref type, so skip it. + return VN_CAST(subdtp->skipRefp(), NodeUOrStructDType); // Maybe nullptr } bool AstMemberSel::same(const AstNode* samep) const { diff --git a/src/V3Class.cpp b/src/V3Class.cpp index c7f167ed8..1df60b5b2 100644 --- a/src/V3Class.cpp +++ b/src/V3Class.cpp @@ -26,6 +26,9 @@ #include "V3UniqueNames.h" +#include +#include + VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### @@ -46,6 +49,11 @@ class ClassVisitor final : public VNVisitor { const AstNodeFTask* m_ftaskp = nullptr; // Current task std::vector> m_toScopeMoves; std::vector> m_toPackageMoves; + std::set m_typedefps; // Contains all typedef nodes + std::set m_strDtypeps; // Contains all packed structs and unions + // Contains all public packed structs and unions, using a queue to + // mark embedded struct / union public by BFS + std::queue m_pubStrDtypeps; // METHODS @@ -204,22 +212,27 @@ class ClassVisitor final : public VNVisitor { dtypep->classOrPackagep(m_classPackagep ? m_classPackagep : m_modp); dtypep->name( m_names.get(dtypep->name() + (VN_IS(dtypep, UnionDType) ? "__union" : "__struct"))); + if (dtypep->packed()) m_strDtypeps.insert(dtypep); for (const AstMemberDType* itemp = dtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { AstNodeUOrStructDType* const subp = itemp->getChildStructp(); - // Recurse only into anonymous unpacked structs inside this definition, - // other unpacked structs will be reached from another typedefs - if (subp && !subp->packed() && subp->name().empty()) setStructModulep(subp); + // Recurse only into anonymous structs inside this definition, + // other structs will be reached from another typedefs + if (subp && subp->name().empty()) setStructModulep(subp); } } void visit(AstTypedef* nodep) override { if (nodep->user1SetOnce()) return; + AstNodeUOrStructDType* const dtypep = VN_CAST(nodep->dtypep(), NodeUOrStructDType); + if (dtypep && dtypep->packed()) { + m_typedefps.insert(nodep); + if (nodep->attrPublic()) m_pubStrDtypeps.push(dtypep); + } iterateChildren(nodep); if (m_classPackagep) m_classPackagep->addStmtsp(nodep->unlinkFrBack()); - AstNodeUOrStructDType* const dtypep = VN_CAST(nodep->dtypep(), NodeUOrStructDType); - if (dtypep && !dtypep->packed()) { + if (dtypep) { dtypep->name(nodep->name()); setStructModulep(dtypep); } @@ -258,6 +271,28 @@ public: nodep->unlinkFrBack(); modp->addStmtsp(nodep); } + // BFS to mark public typedefs. + std::set pubStrDtypeps; + while (!m_pubStrDtypeps.empty()) { + AstNodeUOrStructDType* const dtypep = m_pubStrDtypeps.front(); + m_pubStrDtypeps.pop(); + if (pubStrDtypeps.insert(dtypep).second) { + for (const AstMemberDType* itemp = dtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (AstNodeUOrStructDType* const subp = itemp->getChildStructp()) + m_pubStrDtypeps.push(subp); + } + } + } + for (AstTypedef* typedefp : m_typedefps) { + AstNodeUOrStructDType* const sdtypep = VN_AS(typedefp->dtypep(), NodeUOrStructDType); + if (pubStrDtypeps.count(sdtypep)) typedefp->attrPublic(true); + } + // Clear package pointer of non-public packed struct / union type, which will never be + // exported. + for (AstNodeUOrStructDType* sdtypep : m_strDtypeps) { + if (!pubStrDtypeps.count(sdtypep)) sdtypep->classOrPackagep(nullptr); + } } }; diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 2c52f28dd..eb9ef230c 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -18,6 +18,7 @@ #include "V3EmitC.h" #include "V3EmitCConstInit.h" +#include "V3UniqueNames.h" #include #include @@ -31,6 +32,7 @@ VL_DEFINE_DEBUG_FUNCTIONS; // Internal EmitC implementation class EmitCHeader final : public EmitCConstInit { + V3UniqueNames m_names; // METHODS void decorateFirst(bool& first, const string& str) { @@ -223,7 +225,7 @@ class EmitCHeader final : public EmitCConstInit { for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { AstNodeUOrStructDType* const subp = itemp->getChildStructp(); - if (subp && !subp->packed()) { + if (subp && (!subp->packed() || sdtypep->packed())) { // Recurse if it belongs to the current module if (subp->classOrPackagep() == modp) { emitStructDecl(modp, subp, emitted); @@ -231,6 +233,13 @@ class EmitCHeader final : public EmitCConstInit { } } } + if (sdtypep->packed()) { + emitPackedUOrSBody(sdtypep); + } else { + emitUnpackedUOrSBody(sdtypep); + } + } + void emitUnpackedUOrSBody(AstNodeUOrStructDType* sdtypep) { putns(sdtypep, sdtypep->verilogKwd()); // "struct"/"union" puts(" " + EmitCBase::prefixNameProtect(sdtypep) + " {\n"); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; @@ -254,8 +263,135 @@ class EmitCHeader final : public EmitCConstInit { puts("return !(*this == rhs);\n}\n"); puts("};\n"); } + + // getfunc: VL_ASSIGNSEL_XX(rbits, obits, off, lhsdata, rhsdata); + // !getfunc: VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, off); + void emitVlAssign(const AstNodeDType* const lhstype, const AstNodeDType* rhstype, + const std::string& off, const std::string& lhsdata, + const std::string& rhsdata, bool getfunc) { + puts(getfunc ? "VL_ASSIGNSEL_" : "VL_SELASSIGN_"); + puts(lhstype->charIQWN()); + puts(rhstype->charIQWN()); + puts("(" + std::to_string(lhstype->width()) + ", "); // LHS width + if (getfunc) { + puts(std::to_string(rhstype->width()) + ", "); // Number of copy bits + puts(off + ", "); // LHS offset + } else { + // Number of copy bits. Use widthTototalBytes to + // make VL_SELASSIGN_XX clear upper unused bits for us. + // puts(std::to_string(lhstype->width()) + ", "); + puts(std::to_string(lhstype->widthTotalBytes() * 8) + ", "); + } + puts(lhsdata + ", "); // LHS data + puts(rhsdata); // RHS data + if (!getfunc) { + puts(", " + off); // RHS offset + } + puts(");\n"); + } + + // `retOrArg` should be prefixed by `&` or suffixed by `.data()` depending on its type + void emitPackedMember(const AstNodeDType* parentDtypep, const AstNodeDType* dtypep, + const std::string& fieldname, const std::string& offset, bool getfunc, + const std::string& retOrArg) { + dtypep = dtypep->skipRefp(); + if (const auto* adtypep = VN_CAST(dtypep, PackArrayDType)) { + const std::string index = m_names.get("__Vi"); + puts("for (int " + index + " = 0; " + index + " < " + + std::to_string(adtypep->elementsConst()) + "; ++" + index + ") {\n"); + + const std::string offsetInLoop + = offset + " + " + index + " * " + std::to_string(adtypep->subDTypep()->width()); + const std::string newName = fieldname + "[" + index + "]"; + emitPackedMember(parentDtypep, adtypep->subDTypep(), newName, offsetInLoop, getfunc, + retOrArg); + puts("}\n"); + } else if (VN_IS(dtypep, NodeUOrStructDType)) { + const std::string tmp = m_names.get("__Vtmp"); + const std::string suffixName = dtypep->isWide() ? tmp + ".data()" : tmp; + if (getfunc) { // Emit `get` func; + // auto __tmp = field.get(); + puts("auto " + tmp + " = " + fieldname + ".get();\n"); + // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata); + emitVlAssign(parentDtypep, dtypep, offset, retOrArg, suffixName, getfunc); + } else { // Emit `set` func + const std::string tmptype = AstCDType::typeToHold(dtypep->width()); + // type tmp; + puts(tmptype + " " + tmp + ";\n"); + // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset); + emitVlAssign(dtypep, parentDtypep, offset, suffixName, retOrArg, getfunc); + // field.set(__tmp); + puts(fieldname + ".set(" + tmp + ");\n"); + } + } else { + UASSERT_OBJ(VN_IS(dtypep, EnumDType) || VN_IS(dtypep, BasicDType), dtypep, + "Unsupported type in packed struct or union"); + const std::string suffixName = dtypep->isWide() ? fieldname + ".data()" : fieldname; + if (getfunc) { // Emit `get` func; + // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata); + emitVlAssign(parentDtypep, dtypep, offset, retOrArg, suffixName, getfunc); + } else { // Emit `set` func + // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset); + emitVlAssign(dtypep, parentDtypep, offset, suffixName, retOrArg, getfunc); + } + } + } + void emitPackedUOrSBody(AstNodeUOrStructDType* sdtypep) { + putns(sdtypep, sdtypep->verilogKwd()); // "struct"/"union" + puts(" " + EmitCBase::prefixNameProtect(sdtypep) + " {\n"); + + AstMemberDType* itemp; + AstMemberDType* lastItemp; + AstMemberDType* witemp = nullptr; + // LSB is first field in C, so loop backwards + for (lastItemp = sdtypep->membersp(); lastItemp && lastItemp->nextp(); + lastItemp = VN_AS(lastItemp->nextp(), MemberDType)) { + if (lastItemp->width() == sdtypep->width()) witemp = lastItemp; + } + for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) { + putns(itemp, itemp->dtypep()->cType(itemp->nameProtect(), false, false, true)); + puts(";\n"); + } + + const std::string retArgName = m_names.get("__v"); + const std::string suffixName = sdtypep->isWide() ? retArgName + ".data()" : retArgName; + const std::string retArgType = AstCDType::typeToHold(sdtypep->width()); + + // Emit `get` member function + puts(retArgType + " get() const {\n"); + puts(retArgType + " " + retArgName + ";\n"); + if (VN_IS(sdtypep, StructDType)) { + for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) { + emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(), + std::to_string(itemp->lsb()), /*getfunc=*/true, suffixName); + } + } else { + // We only need to fill the widest field of union + emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(), + std::to_string(witemp->lsb()), /*getfunc=*/true, suffixName); + } + puts("return " + retArgName + ";\n"); + puts("}\n"); + + // Emit `set` member function + puts("void set(const " + retArgType + "& " + retArgName + ") {\n"); + if (VN_IS(sdtypep, StructDType)) { + for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) { + emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(), + std::to_string(itemp->lsb()), /*getfunc=*/false, suffixName); + } + } else { + // We only need to fill the widest field of union + emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(), + std::to_string(witemp->lsb()), /*getfunc=*/false, suffixName); + } + + puts("}\n"); + + puts("};\n"); + m_names.reset(); + } void emitStructs(const AstNodeModule* modp) { - bool first = true; // Track structs that've been emitted already std::set emitted; for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { @@ -264,8 +400,6 @@ class EmitCHeader final : public EmitCConstInit { AstNodeUOrStructDType* const sdtypep = VN_CAST(tdefp->dtypep()->skipRefToEnump(), NodeUOrStructDType); if (!sdtypep) continue; - if (sdtypep->packed()) continue; - decorateFirst(first, "\n// UNPACKED STRUCT TYPES\n"); emitStructDecl(modp, sdtypep, emitted); } } diff --git a/src/V3Name.cpp b/src/V3Name.cpp index ac0579c3d..7e2a5daff 100644 --- a/src/V3Name.cpp +++ b/src/V3Name.cpp @@ -24,6 +24,9 @@ #include "V3Name.h" #include "V3LanguageWords.h" +#include "V3UniqueNames.h" + +#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -41,6 +44,19 @@ class NameVisitor final : public VNVisitorConst { // STATE - for current visit position (use VL_RESTORER) const AstNodeModule* m_modp = nullptr; // Current module + // Rename struct / union field properly + std::vector m_nameStack; // Hierarchy-based renames + + void renameKeywordCheck(AstNode* nodep) { + const std::string rsvd = V3LanguageWords::isKeyword(nodep->name()); + if (rsvd != "") { + nodep->v3warn(SYMRSVDWORD, "Symbol matches " + rsvd + ": " << nodep->prettyNameQ()); + const string newname = "__SYM__"s + nodep->name(); + nodep->name(newname); + nodep->editCountInc(); + } + } + // METHODS void rename(AstNode* nodep, bool addPvt) { if (!nodep->user1()) { // Not already done @@ -50,14 +66,7 @@ class NameVisitor final : public VNVisitorConst { nodep->editCountInc(); } else if (VN_IS(nodep, CFunc) && VN_AS(nodep, CFunc)->isConstructor()) { } else { - const string rsvd = V3LanguageWords::isKeyword(nodep->name()); - if (rsvd != "") { - nodep->v3warn(SYMRSVDWORD, - "Symbol matches " + rsvd + ": " << nodep->prettyNameQ()); - const string newname = "__SYM__"s + nodep->name(); - nodep->name(newname); - nodep->editCountInc(); - } + renameKeywordCheck(nodep); } nodep->user1(1); } @@ -92,9 +101,24 @@ class NameVisitor final : public VNVisitorConst { iterateChildrenConst(nodep); } } + void visit(AstNodeUOrStructDType* nodep) override { + if (nodep->packed()) { + m_nameStack.emplace_back("", false); + m_nameStack.back().get("get"); + m_nameStack.back().get("set"); + } + iterateChildrenConst(nodep); + if (nodep->packed()) m_nameStack.pop_back(); + } void visit(AstMemberDType* nodep) override { if (!nodep->user1()) { - rename(nodep, true); + if (!m_nameStack.empty()) { // Packed member field + renameKeywordCheck(nodep); + nodep->name(m_nameStack.back().get(nodep->name())); + nodep->user1(1); + } else { + rename(nodep, true); + } iterateChildrenConst(nodep); } } diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 9af303973..df344bca7 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -396,6 +396,11 @@ int V3Number::log2b(uint32_t num) { return 0; } +int V3Number::log2bQuad(uint64_t num) { + if (num >> 32ULL) return 32 + log2b(num >> 32ULL); + return log2b(num); +} + //====================================================================== // Setters diff --git a/src/V3Number.h b/src/V3Number.h index 70c63b019..9b63989e5 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -662,6 +662,7 @@ public: // STATICS static int log2b(uint32_t num); + static int log2bQuad(uint64_t num); // MATH // "this" is the output, as we need the output width before some computations diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index ca2107b65..c00800689 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -173,7 +173,8 @@ class RandomizeVisitor final : public VNVisitor { AstCDType* findVlRandCDType(FileLine* fl, uint64_t items) { // For 8 items we need to have a 9 item LFSR so items is max count - const std::string type = AstCDType::typeToHold(items); + // width(items) = log2(items) + 1 + const std::string type = AstCDType::typeToHold(V3Number::log2bQuad(items) + 1); const std::string name = "VlRandC<" + type + ", " + cvtToStr(items) + "ULL>"; // Create or reuse (to avoid duplicates) randomization object dtype const auto pair = m_randcDtypes.emplace(name, nullptr); diff --git a/src/V3UniqueNames.h b/src/V3UniqueNames.h index ec8a0e4ee..92e97f154 100644 --- a/src/V3UniqueNames.h +++ b/src/V3UniqueNames.h @@ -24,18 +24,21 @@ #include "V3Hasher.h" +#include #include -#include class V3UniqueNames final { const std::string m_prefix; // Prefix to attach to all names - std::unordered_map m_multiplicity; // Suffix number for given key + std::map m_multiplicity; // Suffix number for given key + + const bool m_addSuffix = true; // Ad suffix or not public: V3UniqueNames() = default; - explicit V3UniqueNames(const std::string& prefix) - : m_prefix{prefix} { + explicit V3UniqueNames(const std::string& prefix, bool addSuffix = true) + : m_prefix{prefix} + , m_addSuffix(addSuffix) { if (!m_prefix.empty()) { UASSERT(VString::startsWith(m_prefix, "__V"), "Prefix must start with '__V'"); UASSERT(!VString::endsWith(m_prefix, "_"), "Prefix must not end with '_'"); @@ -45,6 +48,15 @@ public: // Return argument, prepended with the prefix if any, then appended with a unique suffix each // time we are called with the same argument. std::string get(const std::string& name) { + if (!m_addSuffix) { + if (m_multiplicity.count(name) == 0) { + m_multiplicity[name] = 0; + return name; + } else { + return get(name + "__" + cvtToStr(m_multiplicity[name]++)); + } + } + // NORMAL mode const unsigned num = m_multiplicity[name]++; std::string result; if (!m_prefix.empty()) { diff --git a/test_regress/t/t_export_packed_struct.cpp b/test_regress/t/t_export_packed_struct.cpp new file mode 100644 index 000000000..552427106 --- /dev/null +++ b/test_regress/t/t_export_packed_struct.cpp @@ -0,0 +1,245 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2024 by Kefa Chen. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +//************************************************************************* + +#include + +#include VM_PREFIX_INCLUDE + +#include "TestCheck.h" + +/* +typedef logic [5:0] udata6_t; + +typedef union packed { + udata6_t a; + logic [2:0] b; +} sub_t; + +typedef struct packed { + logic [40:0] a; + udata6_t [3:0] b; + sub_t c; +} in_t ; + +typedef struct packed { + udata6_t [3:0] b; + sub_t c; + logic [40:0] a; +} out_t ; + +// struct in1_t should cover parts of VL_ASSIGNSEL_II functions +typedef struct packed { + logic [3:0] a; + logic [11:0] b; +} in1_t; // 4 + 12 = 16 + +typedef struct packed { + logic [11:0] b; + logic [3:0] a; +} out1_t; + +// struct in2_t should cover all VL_ASSIGNSEL_II functions +typedef struct packed { + logic [2:0] a; + logic [8:0] b; + logic [18:0] c; +} in2_t; // 3 + 9 + 19 = 31 + +typedef struct packed { + logic [8:0] b; + logic [18:0] c; + logic [2:0] a; +} out2_t; + +// struct in3_t should cover all VL_ASSIGNSEL_XQ functions +typedef struct packed { + logic [1:0] a; + logic [8:0] b; + logic [16:0] c; + logic [32:0] d; +} in3_t; // 33 + 17 + 9 + 2 = 61 + +typedef struct packed { + logic [8:0] b; + logic [1:0] a; + logic [32:0] d; + logic [16:0] c; +} out3_t; + +// struct in4_t should cover all VL_ASSIGNSEL_XW functions +typedef struct packed { + logic [4:0] a; + logic [12:0] b; + logic [24:0] c; + logic [48:0] d; + logic [80:0] e; +} in4_t; // 5 + 13 + 25 + 49 + 81 = 173 + +typedef struct packed { + logic [24:0] c; + logic [48:0] d; + logic [80:0] e; + logic [4:0] a; + logic [12:0] b; +} out4_t; +*/ + +#define CONCAT_IMPL(a, b) a##b +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT5(a, b, c, d, e) CONCAT(CONCAT(CONCAT(CONCAT(a, b), c), d), e) +#define EXPORTED_STRUCT_NAME(STRUCT_NAME, NUMBER) \ + CONCAT5(VM_PREFIX, _, STRUCT_NAME, __struct__, NUMBER) +#define EXPORTED_UNION_NAME(UNION_NAME, NUMBER) \ + CONCAT5(VM_PREFIX, _, UNION_NAME, __union__, NUMBER) +#define SUB_T EXPORTED_UNION_NAME(sub_t, 0) +#define IN_T EXPORTED_STRUCT_NAME(in_t, 0) +#define OUT_T EXPORTED_STRUCT_NAME(out_t, 0) + +#define IN1_T EXPORTED_STRUCT_NAME(in1_t, 0) +#define IN2_T EXPORTED_STRUCT_NAME(in2_t, 0) +#define IN3_T EXPORTED_STRUCT_NAME(in3_t, 0) +#define IN4_T EXPORTED_STRUCT_NAME(in4_t, 0) +#define OUT1_T EXPORTED_STRUCT_NAME(out1_t, 0) +#define OUT2_T EXPORTED_STRUCT_NAME(out2_t, 0) +#define OUT3_T EXPORTED_STRUCT_NAME(out3_t, 0) +#define OUT4_T EXPORTED_STRUCT_NAME(out4_t, 0) + +int errors = 0; + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->debug(0); + contextp->randReset(2); + contextp->commandArgs(argc, argv); + + const std::unique_ptr adder{new VM_PREFIX{contextp.get()}}; + + { + IN_T in1, in2; + OUT_T out; + in1.a = 0x12345678; + in1.__SYM__nullptr[0] = 0x1; + in1.__SYM__nullptr[1] = 0x2; + in1.__SYM__nullptr[2] = 0x3; + in1.__SYM__nullptr[3] = 0x4; + in1.get__0.a = 0x5; + in2.a = 0x11111111; + in2.__SYM__nullptr[0] = 0x10; + in2.__SYM__nullptr[1] = 0x20; + in2.__SYM__nullptr[2] = 0x30; + in2.__SYM__nullptr[3] = 0x30; + in2.get__0.a = 0x20; + + adder->op1 = in1.get(); + adder->op2 = in2.get(); + adder->eval(); + out.set(adder->out); + + TEST_CHECK_EQ(out.__SYM__nullptr[0], 0x11); + TEST_CHECK_EQ(out.__SYM__nullptr[1], 0x22); + TEST_CHECK_EQ(out.__SYM__nullptr[2], 0x33); + TEST_CHECK_EQ(out.__SYM__nullptr[3], 0x34); + TEST_CHECK_EQ(out.get__0.a, 0x25); + TEST_CHECK_EQ(out.a, 0x23456789); + + // Additional tests + IN1_T op1a, op1b; + OUT1_T out1; + + op1a.a = 0x4; + op1b.a = 0x5; + op1a.b = 0x1fe; + op1b.b = 0x1ef; + + adder->op1a = op1a.get(); + adder->op1b = op1b.get(); + adder->eval(); + out1.set(adder->out1); + + TEST_CHECK_EQ(out1.a, op1a.a + op1b.a); + TEST_CHECK_EQ(out1.b, op1a.b + op1b.b); + + IN2_T op2a, op2b; + OUT2_T out2; + + op2a.a = 0x4; + op2b.a = 0x3; + op2a.b = 0xff; + op2b.b = 0x1; + op2a.c = 0x11212; + op2b.c = 0x12121; + + adder->op2a = op2a.get(); + adder->op2b = op2b.get(); + adder->eval(); + out2.set(adder->out2); + + TEST_CHECK_EQ(out2.a, op2a.a + op2b.a); + TEST_CHECK_EQ(out2.b, op2a.b + op2b.b); + TEST_CHECK_EQ(out2.c, op2a.c + op2b.c); + + IN3_T op3a, op3b; + OUT3_T out3; + + op3a.a = 0x1; + op3b.a = 0x2; + op3a.b = 0x155; + op3b.b = 0x44; + op3a.c = 0xff; + op3b.c = 0xff00; + op3a.d = 0x123232323ULL; + op3b.d = 0x32323232ULL; + + adder->op3a = op3a.get(); + adder->op3b = op3b.get(); + adder->eval(); + out3.set(adder->out3); + + TEST_CHECK_EQ(out3.a, op3a.a + op3b.a); + TEST_CHECK_EQ(out3.b, op3a.b + op3b.b); + TEST_CHECK_EQ(out3.c, op3a.c + op3b.c); + TEST_CHECK_EQ(out3.d, op3a.d + op3b.d); + + IN4_T op4a, op4b; + OUT4_T out4; + + op4a.a = 0xf; + op4b.a = 0x2; + op4a.b = 0x123; + op4b.b = 0x432; + op4a.c = 0x123456; + op4b.c = 0x654321; + op4a.d = 0x123456789ULL; + op4b.d = 0x987654321ULL; + op4a.e[0] = 0x12345678; + op4b.e[0] = 0x87654321; + op4a.e[1] = 0xabcde000; + op4b.e[1] = 0x000cdeba; + op4a.e[2] = 0xe; + op4b.e[2] = 0xf; + + adder->op4a = op4a.get(); + adder->op4b = op4b.get(); + adder->eval(); + out4.set(adder->out4); + + TEST_CHECK_EQ(out4.a, op4a.a + op4b.a); + TEST_CHECK_EQ(out4.b, op4a.b + op4b.b); + TEST_CHECK_EQ(out4.c, op4a.c + op4b.c); + TEST_CHECK_EQ(out4.d, op4a.d + op4b.d); + TEST_CHECK_EQ(out4.e[0], op4a.e[0] + op4b.e[0]); + TEST_CHECK_EQ(out4.e[1], op4a.e[1] + op4b.e[1]); + TEST_CHECK_EQ(out4.e[2], op4a.e[2] + op4b.e[2]); + } + + printf("*-* All Finished *-*\n"); + return errors; +} diff --git a/test_regress/t/t_export_packed_struct.pl b/test_regress/t/t_export_packed_struct.pl new file mode 100755 index 000000000..7f8f6c442 --- /dev/null +++ b/test_regress/t/t_export_packed_struct.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp -Wno-SYMRSVDWORD"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_export_packed_struct.v b/test_regress/t/t_export_packed_struct.v new file mode 100644 index 000000000..005038a89 --- /dev/null +++ b/test_regress/t/t_export_packed_struct.v @@ -0,0 +1,133 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Kefa Chen. +// SPDX-License-Identifier: CC0-1.0 + +typedef logic [5:0] udata6_t; + +typedef union packed { + udata6_t a; + logic [2:0] b; +} sub_t; + +typedef struct packed { + logic [40:0] a; + udata6_t [3:0] nullptr; // name confict test + sub_t get; // name confict test +} in_t /*verilator public*/; + +typedef struct packed { + udata6_t [3:0] nullptr; + sub_t get; + logic [40:0] a; +} out_t /*verilator public*/; + +// struct in1_t should cover parts of VL_ASSIGNSEL_II functions +typedef struct packed { + logic [3:0] a; + logic [11:0] b; +} in1_t /*verilator public*/; // 4 + 12 = 16 + +typedef struct packed { + logic [11:0] b; + logic [3:0] a; +} out1_t /*verilator public*/; + +// struct in2_t should cover all VL_ASSIGNSEL_II functions +typedef struct packed { + logic [2:0] a; + logic [8:0] b; + logic [18:0] c; +} in2_t /*verilator public*/; // 3 + 9 + 19 = 31 + +typedef struct packed { + logic [8:0] b; + logic [18:0] c; + logic [2:0] a; +} out2_t /*verilator public*/; + +// struct in3_t should cover all VL_ASSIGNSEL_XQ functions +typedef struct packed { + logic [1:0] a; + logic [8:0] b; + logic [16:0] c; + logic [32:0] d; +} in3_t /*verilator public*/; // 33 + 17 + 9 + 2 = 61 + +typedef struct packed { + logic [8:0] b; + logic [1:0] a; + logic [32:0] d; + logic [16:0] c; +} out3_t /*verilator public*/; + +// struct in4_t should cover all VL_ASSIGNSEL_XW functions +typedef struct packed { + logic [4:0] a; + logic [12:0] b; + logic [24:0] c; + logic [48:0] d; + logic [80:0] e; +} in4_t /*verilator public*/; // 5 + 13 + 25 + 49 + 81 = 173 + +typedef struct packed { + logic [24:0] c; + logic [48:0] d; + logic [80:0] e; + logic [4:0] a; + logic [12:0] b; +} out4_t /*verilator public*/; + +module add ( + input in_t op1, + input in_t op2, + output out_t out, + // Add some extra ports to test all VL_ASSIGNSEL_XX functions + input in1_t op1a, + input in1_t op1b, + output out1_t out1, + // Add some extra ports to test all VL_ASSIGNSEL_XX functions + input in2_t op2a, + input in2_t op2b, + output out2_t out2, + // Add some extra ports to test all VL_ASSIGNSEL_XX functions + input in3_t op3a, + input in3_t op3b, + output out3_t out3, + // Add some extra ports to test all VL_ASSIGNSEL_XX functions + input in4_t op4a, + input in4_t op4b, + output out4_t out4 +); + assign out.a = op1.a + op2.a; + generate + for (genvar i = 0; i < 4; ++i) begin + assign out.nullptr[i] = op1.nullptr[i] + op2.nullptr[i]; + end + endgenerate + assign out.get.a = op1.get.a + op2.get.a; + + // out1 + assign out1.a = op1a.a + op1b.a; + assign out1.b = op1a.b + op1b.b; + + // out2 + assign out2.a = op2a.a + op2b.a; + assign out2.b = op2a.b + op2b.b; + assign out2.c = op2a.c + op2b.c; + + // out3 + assign out3.a = op3a.a + op3b.a; + assign out3.b = op3a.b + op3b.b; + assign out3.c = op3a.c + op3b.c; + assign out3.d = op3a.d + op3b.d; + + // out4 + assign out4.a = op4a.a + op4b.a; + assign out4.b = op4a.b + op4b.b; + assign out4.c = op4a.c + op4b.c; + assign out4.d = op4a.d + op4b.d; + assign out4.e = op4a.e + op4b.e; + +endmodule diff --git a/test_regress/t/t_export_packed_struct2.cpp b/test_regress/t/t_export_packed_struct2.cpp new file mode 100644 index 000000000..6357902ef --- /dev/null +++ b/test_regress/t/t_export_packed_struct2.cpp @@ -0,0 +1,122 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2024 by Kefa Chen. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +//************************************************************************* + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE +#include VM_PREFIX_ROOT_INCLUDE + +#include "TestCheck.h" +#include "Vt_export_packed_struct2___024unit__03a__03acls_in__Vclpkg.h" + +/* +// Packed struct in package +package TEST_TYPES; + typedef union packed { + logic [64:0] a; + logic [2:0] b; + } sub_t; + typedef struct packed { + struct packed { // Anonymous packed struct + logic a; + } anon; + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + } in_t; + typedef struct packed { + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + struct packed {logic a;} anon; + } out_t; +endpackage + +// Packed struct in class +class cls_in; + typedef struct packed { + logic a; + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + } in_t; + in_t in; +endclass //cls +*/ + +#define CONCAT_IMPL(a, b) a##b +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT5(a, b, c, d, e) CONCAT(CONCAT(CONCAT(CONCAT(a, b), c), d), e) +#define EXPORTED_STRUCT_NAME(STRUCT_NAME, NUMBER) \ + CONCAT5(VM_PREFIX, _, STRUCT_NAME, __struct__, NUMBER) +#define EXPORTED_UNION_NAME(UNION_NAME, NUMBER) \ + CONCAT5(VM_PREFIX, _, UNION_NAME, __union__, NUMBER) +#define SUB_T EXPORTED_UNION_NAME(sub_t, 0) +#define IN_T EXPORTED_STRUCT_NAME(in_t, 0) +#define IN2_T EXPORTED_STRUCT_NAME(in_t, 1) +#define OUT_T EXPORTED_STRUCT_NAME(out_t, 0) + +int errors = 0; + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->debug(0); + contextp->randReset(2); + contextp->commandArgs(argc, argv); + + const std::unique_ptr adder{new VM_PREFIX{contextp.get()}}; + + { + IN_T in; + IN2_T tmp; + OUT_T out; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < 3; ++k) { + VL_SET_WQ(in.b[i][j][k].a, 0x1234123412341234UL); + // Set last bit zero and upper bits one + in.b[i][j][k].a[2] = 0xfe; + } + } + } + in.anon.a = 0x1; + + adder->op1 = in.get(); + adder->eval(); + out.set(adder->out); + + std::memset(reinterpret_cast(&tmp), 0xff, sizeof(tmp)); + // `set` function should clear upper bits of `tmp.a` + tmp.set(adder->rootp->add__DOT__op2->__PVT__in); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < 3; ++k) { + TEST_CHECK_EQ(tmp.b[i][j][k].a[0], 0x12341234); + TEST_CHECK_EQ(tmp.b[i][j][k].a[1], 0x12341234); + TEST_CHECK_EQ(tmp.b[i][j][k].a[2], 0); + } + } + } + TEST_CHECK_EQ(tmp.a, 0x1); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < 3; ++k) { + TEST_CHECK_EQ(out.b[i][j][k].a[0], 0x24682468); + TEST_CHECK_EQ(out.b[i][j][k].a[1], 0x24682468); + TEST_CHECK_EQ(out.b[i][j][k].a[2], 0x0); + } + } + } + TEST_CHECK_EQ(out.anon.a, 0x0); + } + + printf("*-* All Finished *-*\n"); + return errors; +} diff --git a/test_regress/t/t_export_packed_struct2.pl b/test_regress/t/t_export_packed_struct2.pl new file mode 100755 index 000000000..8ff32055a --- /dev/null +++ b/test_regress/t/t_export_packed_struct2.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_export_packed_struct2.v b/test_regress/t/t_export_packed_struct2.v new file mode 100644 index 000000000..b63206cbe --- /dev/null +++ b/test_regress/t/t_export_packed_struct2.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Kefa Chen. +// SPDX-License-Identifier: CC0-1.0 + +// Packed struct in package +package TEST_TYPES; + typedef union packed { + logic [64:0] a; + logic [2:0] b; + } sub_t; + typedef struct packed { + struct packed { // Anonymous packed struct + logic a; + } anon; + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + } in_t /*verilator public*/; + typedef struct packed { + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + struct packed {logic a;} anon; + } out_t /*verilator public*/; +endpackage + +// Packed struct in class +class cls_in; + typedef struct packed { + logic a; + TEST_TYPES::sub_t [2:0][2:0][2:0] b; + } in_t /*verilator public*/; + in_t in; +endclass //cls + +module add ( + input TEST_TYPES::in_t op1, + //input cls_in op2, + output TEST_TYPES::out_t out +); + cls_in op2 /*verilator public_flat*/; + + initial begin + if(op2 != null) $stop; + op2 = new(); + if(!op2) $stop; + end + + assign op2.in.a = op1.anon.a; + generate + for (genvar i = 0; i < 3; ++i) begin + for (genvar j = 0; j < 3; ++j) begin + for (genvar k = 0; k < 3; ++k) begin + assign op2.in.b[i][j][k] = op1.b[i][j][k]; + end + end + end + endgenerate + + assign out.anon.a = op1.anon.a + op2.in.a; + generate + for (genvar i = 0; i < 3; ++i) begin + for (genvar j = 0; j < 3; ++j) begin + for (genvar k = 0; k < 3; ++k) begin + assign out.b[i][j][k] = op1.b[i][j][k] + op2.in.b[i][j][k]; + end + end + end + endgenerate + +endmodule