From 542e3248695af6f776457152a9c3b84904b65727 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Wed, 20 Jul 2022 15:01:36 +0200 Subject: [PATCH] Wildcard index type support for associative arrays (#3501). Associative arrays that specify a wildcard index type may be indexed by integral expressions of any size, with leading zeros removed automatically. A natural representation for such expressions is a string, especially that the standard explicitly specifies automatic casts from string indices to bit vectors of equivalent size. The automatic cast part is done implicitly by the existing type system. A simpler way to just make this work would be to convert wildcard index type to a string type directly in the parser code, but several new AST classes are needed to make sure illegal method calls are detected. The verilated data structure implementation is reused, because there is no need for differentiating the behavior on C++ side. --- src/V3AstNodes.cpp | 7 + src/V3AstNodes.h | 146 +++++++++++++++ src/V3Clean.cpp | 1 + src/V3EmitCFunc.cpp | 16 ++ src/V3EmitCFunc.h | 26 +++ src/V3Hasher.cpp | 5 + src/V3ParseGrammar.cpp | 2 + src/V3Width.cpp | 170 ++++++++++++++++++ src/V3WidthSel.cpp | 10 ++ src/verilog.y | 6 +- test_regress/t/t_assoc_wildcard.pl | 21 +++ ...oc_wildcard_unsup.v => t_assoc_wildcard.v} | 21 ++- test_regress/t/t_assoc_wildcard_bad.out | 73 ++++++++ ...dcard_unsup.pl => t_assoc_wildcard_bad.pl} | 0 test_regress/t/t_assoc_wildcard_bad.v | 45 +++++ test_regress/t/t_assoc_wildcard_method.pl | 21 +++ test_regress/t/t_assoc_wildcard_method.v | 127 +++++++++++++ test_regress/t/t_assoc_wildcard_unsup.out | 5 - 18 files changed, 682 insertions(+), 20 deletions(-) create mode 100755 test_regress/t/t_assoc_wildcard.pl rename test_regress/t/{t_assoc_wildcard_unsup.v => t_assoc_wildcard.v} (71%) create mode 100644 test_regress/t/t_assoc_wildcard_bad.out rename test_regress/t/{t_assoc_wildcard_unsup.pl => t_assoc_wildcard_bad.pl} (100%) create mode 100644 test_regress/t/t_assoc_wildcard_bad.v create mode 100755 test_regress/t/t_assoc_wildcard_method.pl create mode 100644 test_regress/t/t_assoc_wildcard_method.v delete mode 100644 test_regress/t/t_assoc_wildcard_unsup.out diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 33d275e4e..95ce8ba24 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -673,6 +673,9 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { const CTypeRecursed key = adtypep->keyDTypep()->cTypeRecurse(true); const CTypeRecursed val = adtypep->subDTypep()->cTypeRecurse(true); info.m_type = "VlAssocArray<" + key.m_type + ", " + val.m_type + ">"; + } else if (const auto* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + info.m_type = "VlAssocArray"; } else if (const auto* const adtypep = VN_CAST(dtypep, DynArrayDType)) { const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); info.m_type = "VlQueue<" + sub.m_type + ">"; @@ -1683,6 +1686,10 @@ string AstQueueDType::prettyDTypeName() const { if (boundConst()) str += ":" + cvtToStr(boundConst()); return str + "]"; } +void AstWildcardArrayDType::dumpSmall(std::ostream& str) const { + this->AstNodeDType::dumpSmall(str); + str << "[*]"; +} void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const { this->AstNodeDType::dumpSmall(str); str << "[]"; diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index d9e4b81db..8b258db0a 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -278,6 +278,17 @@ public: virtual bool same(const AstNode* samep) const override { return true; } }; +class AstWildcardRange final : public AstNodeRange { + // Wildcard range specification, for wildcard index type associative arrays +public: + explicit AstWildcardRange(FileLine* fl) + : ASTGEN_SUPER_WildcardRange(fl) {} + ASTNODE_NODE_FUNCS(WildcardRange) + virtual string emitC() { V3ERROR_NA_RETURN(""); } + virtual string emitVerilog() { return "[*]"; } + virtual bool same(const AstNode* samep) const override { return true; } +}; + class AstGatePin final : public AstNodeMath { // Possibly expand a gate primitive input pin value to match the range of the gate primitive public: @@ -819,6 +830,62 @@ public: virtual bool isCompound() const override { return true; } }; +class AstWildcardArrayDType final : public AstNodeDType { + // Wildcard index type associative array data type, ie "some_dtype var_name [*]" + // Children: DTYPE (moved to refDTypep() in V3Width) +private: + AstNodeDType* m_refDTypep; // Elements of this type (after widthing) +public: + AstWildcardArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp) + : ASTGEN_SUPER_WildcardArrayDType(fl) { + childDTypep(dtp); // Only for parser + refDTypep(nullptr); + dtypep(nullptr); // V3Width will resolve + } + ASTNODE_NODE_FUNCS(WildcardArrayDType) + virtual const char* broken() const override { + BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) + || (!m_refDTypep && childDTypep()))); + return nullptr; + } + virtual void cloneRelink() override { + if (m_refDTypep && m_refDTypep->clonep()) m_refDTypep = m_refDTypep->clonep(); + } + virtual bool same(const AstNode* samep) const override { + const AstNodeArrayDType* const asamep = static_cast(samep); + if (!asamep->subDTypep()) return false; + return (subDTypep() == asamep->subDTypep()); + } + virtual bool similarDType(AstNodeDType* samep) const override { + const AstNodeArrayDType* const asamep = static_cast(samep); + return type() == samep->type() && asamep->subDTypep() + && subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp()); + } + virtual void dumpSmall(std::ostream& str) const override; + virtual AstNodeDType* getChildDTypep() const override { return childDTypep(); } + // op1 = Range of variable + AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } + void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } + virtual AstNodeDType* subDTypep() const override { + return m_refDTypep ? m_refDTypep : childDTypep(); + } + void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } + virtual AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } + virtual void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } + // METHODS + virtual AstBasicDType* basicp() const override { return subDTypep()->basicp(); } + virtual AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } + virtual int widthAlignBytes() const override { + return sizeof(std::map); + } + virtual int widthTotalBytes() const override { + return sizeof(std::map); + } + virtual bool isCompound() const override { return true; } +}; + class AstBasicDType final : public AstNodeDType { // Builtin atomic/vectored data type // Children: RANGE (converted to constant in V3Width) @@ -1686,6 +1753,44 @@ public: virtual int instrCount() const override { return widthInstrs(); } }; +class AstWildcardSel final : public AstNodeSel { + // Parents: math|stmt + // Children: varref|arraysel, math +private: + void init(AstNode* fromp) { + if (fromp && VN_IS(fromp->dtypep()->skipRefp(), WildcardArrayDType)) { + // Strip off array to find what array references + dtypeFrom(VN_AS(fromp->dtypep()->skipRefp(), WildcardArrayDType)->subDTypep()); + } + } + +public: + AstWildcardSel(FileLine* fl, AstNode* fromp, AstNode* bitp) + : ASTGEN_SUPER_WildcardSel(fl, fromp, bitp) { + init(fromp); + } + ASTNODE_NODE_FUNCS(WildcardSel) + virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { + return new AstWildcardSel{this->fileline(), lhsp, rhsp}; + } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { + V3ERROR_NA; + } + virtual string emitVerilog() override { return "%k(%l%f[%r])"; } + virtual string emitC() override { return "%li%k[%ri]"; } + virtual bool cleanOut() const override { return true; } + virtual bool cleanLhs() const override { return false; } + virtual bool cleanRhs() const override { return true; } + virtual bool sizeMattersLhs() const override { return false; } + virtual bool sizeMattersRhs() const override { return false; } + virtual bool isGateOptimizable() const override { + return true; + } // esp for V3Const::ifSameAssign + virtual bool isPredictOptimizable() const override { return false; } + virtual bool same(const AstNode* samep) const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } +}; + class AstWordSel final : public AstNodeSel { // Select a single word from a multi-word wide value public: @@ -4887,6 +4992,47 @@ public: virtual bool same(const AstNode* samep) const override { return true; } }; +class AstConsWildcard final : public AstNodeMath { + // Construct a wildcard assoc array and return object, '{} + // Parents: math + // Children: expression (elements or other queues) +public: + AstConsWildcard(FileLine* fl, AstNode* defaultp) + : ASTGEN_SUPER_ConsWildcard(fl) { + setNOp1p(defaultp); + } + ASTNODE_NODE_FUNCS(ConsWildcard) + virtual string emitVerilog() override { return "'{}"; } + virtual string emitC() override { V3ERROR_NA_RETURN(""); } + virtual string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + virtual bool cleanOut() const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } + AstNode* defaultp() const { return op1p(); } + virtual bool same(const AstNode* samep) const override { return true; } +}; +class AstSetWildcard final : public AstNodeMath { + // Set a wildcard assoc array element and return object, '{} + // Parents: math + // Children: expression (elements or other queues) +public: + AstSetWildcard(FileLine* fl, AstNode* lhsp, AstNode* keyp, AstNode* valuep) + : ASTGEN_SUPER_SetWildcard(fl) { + setOp1p(lhsp); + setNOp2p(keyp); + setOp3p(valuep); + } + ASTNODE_NODE_FUNCS(SetWildcard) + virtual string emitVerilog() override { return "'{}"; } + virtual string emitC() override { V3ERROR_NA_RETURN(""); } + virtual string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + virtual bool cleanOut() const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } + AstNode* lhsp() const { return op1p(); } + AstNode* keyp() const { return op2p(); } + AstNode* valuep() const { return op3p(); } + virtual bool same(const AstNode* samep) const override { return true; } +}; + class AstConsDynArray final : public AstNodeMath { // Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs} // Parents: math diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 613b681d7..eaf7c1f39 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -88,6 +88,7 @@ private: if (VN_IS(nodep, Var) || VN_IS(nodep, NodeDType) // Don't want to change variable widths! || VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType) // Or arrays + || VN_IS(nodep->dtypep()->skipRefp(), WildcardArrayDType) || VN_IS(nodep->dtypep()->skipRefp(), DynArrayDType) || VN_IS(nodep->dtypep()->skipRefp(), ClassRefDType) || VN_IS(nodep->dtypep()->skipRefp(), QueueDType) diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index ae3f8ce03..7117fee7e 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -611,6 +611,17 @@ void EmitCFunc::emitVarReset(AstVar* varp) { emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")", VN_AS(valuep, Const)); } + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + if (initarp->defaultp()) { + emitSetVarConstant(varNameProtected + ".atDefault()", + VN_AS(initarp->defaultp(), Const)); + } + const auto& mapr = initarp->map(); + for (const auto& itr : mapr) { + AstNode* const valuep = itr.second->valuep(); + emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")", + VN_AS(valuep, Const)); + } } else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { if (initarp->defaultp()) { puts("for (int __Vi=0; __Vi<" + cvtToStr(adtypep->elementsConst())); @@ -642,6 +653,11 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1, suffix + ".atDefault()" + cvtarray); + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + // Access std::array as C array + const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); + return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1, + suffix + ".atDefault()" + cvtarray); } else if (VN_IS(dtypep, ClassRefDType)) { return ""; // Constructor does it } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 629b90397..31d1f5a11 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -379,6 +379,14 @@ public: } puts(")"); } + virtual void visit(AstWildcardSel* nodep) override { + iterateAndNextNull(nodep->fromp()); + putbs(".at("); + AstWildcardArrayDType* const adtypep = VN_AS(nodep->fromp()->dtypep(), WildcardArrayDType); + UASSERT_OBJ(adtypep, nodep, "Wildcard select on non-wildcard-associative type"); + iterateAndNextNull(nodep->bitp()); + puts(")"); + } virtual void visit(AstCCall* nodep) override { const AstCFunc* const funcp = nodep->funcp(); const AstNodeModule* const funcModp = EmitCParentModule::get(funcp); @@ -1189,6 +1197,24 @@ public: iterateAndNextNull(nodep->valuep()); puts(")"); } + virtual void visit(AstConsWildcard* nodep) override { + putbs(nodep->dtypep()->cType("", false, false)); + puts("()"); + if (nodep->defaultp()) { + putbs(".setDefault("); + iterateAndNextNull(nodep->defaultp()); + puts(")"); + } + } + virtual void visit(AstSetWildcard* nodep) override { + iterateAndNextNull(nodep->lhsp()); + putbs(".set("); + iterateAndNextNull(nodep->keyp()); + puts(", "); + putbs(""); + iterateAndNextNull(nodep->valuep()); + puts(")"); + } virtual void visit(AstConsDynArray* nodep) override { putbs(nodep->dtypep()->cType("", false, false)); if (!nodep->lhsp()) { diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp index 4b0cbbbba..ba59ea462 100644 --- a/src/V3Hasher.cpp +++ b/src/V3Hasher.cpp @@ -133,6 +133,11 @@ private: iterateNull(nodep->virtRefDTypep()); }); } + virtual void visit(AstWildcardArrayDType* nodep) override { + m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { // + iterateNull(nodep->virtRefDTypep()); + }); + } virtual void visit(AstBasicDType* nodep) override { m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { m_hash += nodep->keyword(); diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index 2cc65af6a..f2d57f289 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -138,6 +138,8 @@ AstNodeDType* V3ParseGrammar::createArray(AstNodeDType* basep, AstNodeRange* nra AstNode* const keyp = arangep->elementsp()->unlinkFrBack(); arrayp = new AstBracketArrayDType(nrangep->fileline(), VFlagChildDType(), arrayp, keyp); + } else if (VN_IS(nrangep, WildcardRange)) { + arrayp = new AstWildcardArrayDType{nrangep->fileline(), VFlagChildDType{}, arrayp}; } else { UASSERT_OBJ(0, nrangep, "Expected range or unsized range"); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 8a833d9f2..c98e856aa 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -981,6 +981,27 @@ private: } } + virtual void visit(AstWildcardSel* nodep) override { + // Signed/Real: Output type based on array-declared type; binary operator + if (m_vup->prelim()) { + const AstNodeDType* const fromDtp = nodep->fromp()->dtypep()->skipRefp(); + const AstWildcardArrayDType* const adtypep = VN_CAST(fromDtp, WildcardArrayDType); + if (!adtypep) { + UINFO(1, " Related dtype: " << fromDtp << endl); + nodep->v3fatalSrc("Wildcard array reference is not to wildcard array"); + } + const AstBasicDType* const basicp = nodep->bitp()->dtypep()->skipRefp()->basicp(); + if (!basicp + || (basicp->keyword() != VBasicDTypeKwd::STRING + && !basicp->keyword().isIntNumeric())) { + nodep->v3error("Wildcard index must be integral (IEEE 1800-2017 7.8.1)"); + } + iterateCheckTyped(nodep, "Wildcard associative select", nodep->bitp(), + adtypep->findStringDType(), BOTH); + nodep->dtypeFrom(adtypep->subDTypep()); + } + } + virtual void visit(AstSliceSel* nodep) override { // Always creates as output an unpacked array if (m_vup->prelim()) { @@ -1582,6 +1603,14 @@ private: nodep->dtypep(nodep); // The array itself, not subDtype UINFO(4, "dtWidthed " << nodep << endl); } + virtual void visit(AstWildcardArrayDType* nodep) override { + if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed + // Iterate into subDTypep() to resolve that type and update pointer. + nodep->refDTypep(iterateEditMoveDTypep(nodep, nodep->subDTypep())); + // Cleanup array size + nodep->dtypep(nodep); // The array itself, not subDtype + UINFO(4, "dtWidthed " << nodep << endl); + } virtual void visit(AstBasicDType* nodep) override { if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed if (nodep->generic()) return; // Already perfect @@ -2178,6 +2207,31 @@ private: EXTEND_EXP); } } + virtual void visit(AstConsWildcard* nodep) override { + // Type computed when constructed here + auto* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), WildcardArrayDType); + UASSERT_OBJ(vdtypep, nodep, "ConsWildcard requires wildcard upper parent data type"); + if (m_vup->prelim()) { + nodep->dtypeFrom(vdtypep); + if (nodep->defaultp()) { + iterateCheck(nodep, "default", nodep->defaultp(), CONTEXT, FINAL, + vdtypep->subDTypep(), EXTEND_EXP); + } + } + } + virtual void visit(AstSetWildcard* nodep) override { + // Type computed when constructed here + auto* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), WildcardArrayDType); + UASSERT_OBJ(vdtypep, nodep, "SetWildcard requires wildcard upper parent data type"); + if (m_vup->prelim()) { + nodep->dtypeFrom(vdtypep); + userIterateAndNext(nodep->lhsp(), WidthVP{vdtypep, BOTH}.p()); + iterateCheck(nodep, "key", nodep->keyp(), CONTEXT, FINAL, vdtypep->findStringDType(), + EXTEND_EXP); + iterateCheck(nodep, "value", nodep->valuep(), CONTEXT, FINAL, vdtypep->subDTypep(), + EXTEND_EXP); + } + } virtual void visit(AstConsDynArray* nodep) override { // Type computed when constructed here AstDynArrayDType* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), DynArrayDType); @@ -2426,6 +2480,7 @@ private: } } else if (VN_IS(fromDtp, EnumDType) // || VN_IS(fromDtp, AssocArrayDType) // + || VN_IS(fromDtp, WildcardArrayDType) // || VN_IS(fromDtp, UnpackArrayDType) // || VN_IS(fromDtp, DynArrayDType) // || VN_IS(fromDtp, QueueDType) // @@ -2523,6 +2578,8 @@ private: methodCallEnum(nodep, adtypep); } else if (AstAssocArrayDType* const adtypep = VN_CAST(fromDtp, AssocArrayDType)) { methodCallAssoc(nodep, adtypep); + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(fromDtp, WildcardArrayDType)) { + methodCallWildcard(nodep, adtypep); } else if (AstDynArrayDType* const adtypep = VN_CAST(fromDtp, DynArrayDType)) { methodCallDyn(nodep, adtypep); } else if (AstQueueDType* const adtypep = VN_CAST(fromDtp, QueueDType)) { @@ -2684,6 +2741,89 @@ private: nodep->v3error("Unknown built-in enum method " << nodep->prettyNameQ()); } } + void methodCallWildcard(AstMethodCall* nodep, AstWildcardArrayDType* adtypep) { + AstCMethodHard* newp = nullptr; + if (nodep->name() == "num" // function int num() + || nodep->name() == "size") { + methodOkArguments(nodep, 0, 0); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "size"}; // So don't need num() + newp->dtypeSetSigned32(); + } else if (nodep->name() == "first" // function int first(ref index) + || nodep->name() == "last" // + || nodep->name() == "next" // + || nodep->name() == "prev" // + || nodep->name() == "unique_index" // + || nodep->name() == "find_index" || nodep->name() == "find_first_index" + || nodep->name() == "find_last_index") { + nodep->v3error("Array method " << nodep->prettyNameQ() + << " not legal on wildcard associative arrays"); + } else if (nodep->name() == "exists") { // function int exists(input index) + // IEEE really should have made this a "bit" return + methodOkArguments(nodep, 1, 1); + AstNode* const index_exprp = methodCallWildcardIndexExpr(nodep, adtypep); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), "exists", + index_exprp->unlinkFrBack()}; + newp->dtypeSetSigned32(); + newp->pure(true); + } else if (nodep->name() == "delete") { // function void delete([input integer index]) + methodOkArguments(nodep, 0, 1); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::WRITE); + if (!nodep->pinsp()) { + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "clear"}; + newp->makeStatement(); + } else { + AstNode* const index_exprp = methodCallWildcardIndexExpr(nodep, adtypep); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "erase", index_exprp->unlinkFrBack()}; + newp->makeStatement(); + } + } else if (nodep->name() == "sort" || nodep->name() == "rsort" + || nodep->name() == "reverse" || nodep->name() == "shuffle") { + nodep->v3error("Array method " << nodep->prettyNameQ() + << " not legal on associative arrays"); + } else if (nodep->name() == "and" || nodep->name() == "or" || nodep->name() == "xor" + || nodep->name() == "sum" || nodep->name() == "product") { + // All value return + AstWith* const withp + = methodWithArgument(nodep, false, false, adtypep->subDTypep(), + adtypep->findStringDType(), adtypep->subDTypep()); + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "r_" + nodep->name(), withp}; + newp->dtypeFrom(withp ? withp->dtypep() : adtypep->subDTypep()); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else if (nodep->name() == "min" || nodep->name() == "max" || nodep->name() == "unique") { + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + nodep->name()}; + newp->dtypeFrom(adtypep); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else if (nodep->name() == "find" || nodep->name() == "find_first" + || nodep->name() == "find_last") { + AstWith* const withp + = methodWithArgument(nodep, true, false, nodep->findBitDType(), + adtypep->findStringDType(), adtypep->subDTypep()); + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + nodep->name(), withp}; + newp->dtypeFrom(adtypep); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else { + nodep->v3error("Unknown wildcard associative array method " << nodep->prettyNameQ()); + nodep->dtypeFrom(adtypep->subDTypep()); // Best guess + } + if (newp) { + newp->protect(false); + newp->didWidth(true); + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + } void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { AstCMethodHard* newp = nullptr; if (nodep->name() == "num" // function int num() @@ -2789,6 +2929,13 @@ private: VL_DANGLING(index_exprp); // May have been edited return VN_AS(nodep->pinsp(), Arg)->exprp(); } + AstNode* methodCallWildcardIndexExpr(AstMethodCall* nodep, AstWildcardArrayDType* adtypep) { + AstNode* const index_exprp = VN_CAST(nodep->pinsp(), Arg)->exprp(); + iterateCheck(nodep, "index", index_exprp, CONTEXT, FINAL, adtypep->findStringDType(), + EXTEND_EXP); + VL_DANGLING(index_exprp); // May have been edited + return VN_AS(nodep->pinsp(), Arg)->exprp(); + } void methodCallLValueRecurse(AstMethodCall* nodep, AstNode* childp, const VAccess& access) { if (AstNodeVarRef* const varrefp = VN_CAST(childp, NodeVarRef)) { varrefp->access(access); @@ -3393,6 +3540,8 @@ private: VL_DO_DANGLING(patternArray(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, AssocArrayDType)) { VL_DO_DANGLING(patternAssoc(nodep, vdtypep, defaultp), nodep); + } else if (auto* const vdtypep = VN_CAST(dtypep, WildcardArrayDType)) { + VL_DO_DANGLING(patternWildcard(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, DynArrayDType)) { VL_DO_DANGLING(patternDynArray(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, QueueDType)) { @@ -3576,6 +3725,26 @@ private: // if (debug() >= 9) newp->dumpTree("-apat-out: "); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present } + void patternWildcard(AstPattern* nodep, AstWildcardArrayDType* arrayDtp, + AstPatMember* defaultp) { + AstNode* defaultValuep = nullptr; + if (defaultp) defaultValuep = defaultp->lhssp()->unlinkFrBack(); + AstNode* newp = new AstConsWildcard{nodep->fileline(), defaultValuep}; + newp->dtypeFrom(arrayDtp); + for (AstPatMember* patp = VN_AS(nodep->itemsp(), PatMember); patp; + patp = VN_AS(patp->nextp(), PatMember)) { + patp->dtypep(arrayDtp->subDTypep()); + AstNode* const valuep = patternMemberValueIterate(patp); + AstNode* const keyp = patp->keyp(); + auto* const newap + = new AstSetWildcard{nodep->fileline(), newp, keyp->unlinkFrBack(), valuep}; + newap->dtypeFrom(arrayDtp); + newp = newap; + } + nodep->replaceWith(newp); + // if (debug() >= 9) newp->dumpTree("-apat-out: "); + VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present + } void patternDynArray(AstPattern* nodep, AstDynArrayDType* arrayp, AstPatMember*) { AstNode* newp = new AstConsDynArray(nodep->fileline()); newp->dtypeFrom(arrayp); @@ -4092,6 +4261,7 @@ private: added = true; newFormat += "%g"; } else if (VN_IS(dtypep, AssocArrayDType) // + || VN_IS(dtypep, WildcardArrayDType) // || VN_IS(dtypep, ClassRefDType) // || VN_IS(dtypep, DynArrayDType) // || VN_IS(dtypep, QueueDType)) { diff --git a/src/V3WidthSel.cpp b/src/V3WidthSel.cpp index 793b98aec..4d6b7de01 100644 --- a/src/V3WidthSel.cpp +++ b/src/V3WidthSel.cpp @@ -88,6 +88,7 @@ private: if (const AstNodeArrayDType* const adtypep = VN_CAST(ddtypep, NodeArrayDType)) { fromRange = adtypep->declRange(); } else if (VN_IS(ddtypep, AssocArrayDType)) { + } else if (VN_IS(ddtypep, WildcardArrayDType)) { } else if (VN_IS(ddtypep, DynArrayDType)) { } else if (VN_IS(ddtypep, QueueDType)) { } else if (const AstNodeUOrStructDType* const adtypep @@ -257,6 +258,15 @@ private: if (debug() >= 9) newp->dumpTree(cout, "--SELBTn: "); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else if (const AstWildcardArrayDType* const adtypep + = VN_CAST(ddtypep, WildcardArrayDType)) { + // SELBIT(array, index) -> WILDCARDSEL(array, index) + AstNode* const subp = rhsp; + AstWildcardSel* const newp = new AstWildcardSel{nodep->fileline(), fromp, subp}; + newp->dtypeFrom(adtypep->subDTypep()); // Need to strip off array reference + if (debug() >= 9) newp->dumpTree(cout, "--SELBTn: "); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (const AstDynArrayDType* const adtypep = VN_CAST(ddtypep, DynArrayDType)) { // SELBIT(array, index) -> CMETHODCALL(queue, "at", index) AstNode* const subp = rhsp; diff --git a/src/verilog.y b/src/verilog.y index 34fbe86b0..82faf4a7d 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2074,10 +2074,8 @@ variable_dimension: // ==IEEE: variable_dimension // // IEEE: associative_dimension (if data_type) // // Can't tell which until see if expr is data type or not | '[' exprOrDataType ']' { $$ = new AstBracketRange($1, $2); } - | yP_BRASTAR ']' - { $$ = nullptr; BBUNSUP($1, "Unsupported: [*] wildcard associative arrays"); } - | '[' '*' ']' - { $$ = nullptr; BBUNSUP($2, "Unsupported: [*] wildcard associative arrays"); } + | yP_BRASTAR ']' { $$ = new AstWildcardRange{$1}; } + | '[' '*' ']' { $$ = new AstWildcardRange{$1}; } // // IEEE: queue_dimension // // '[' '$' ']' -- $ is part of expr, see '[' constExpr ']' // // '[' '$' ':' expr ']' -- anyrange:expr:$ diff --git a/test_regress/t/t_assoc_wildcard.pl b/test_regress/t/t_assoc_wildcard.pl new file mode 100755 index 000000000..9a15dd2cc --- /dev/null +++ b/test_regress/t/t_assoc_wildcard.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 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(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_wildcard_unsup.v b/test_regress/t/t_assoc_wildcard.v similarity index 71% rename from test_regress/t/t_assoc_wildcard_unsup.v rename to test_regress/t/t_assoc_wildcard.v index d17eaf3c1..2d4e5c782 100644 --- a/test_regress/t/t_assoc_wildcard_unsup.v +++ b/test_regress/t/t_assoc_wildcard.v @@ -22,26 +22,25 @@ module t (/*AUTOARG*/ cyc <= cyc + 1; begin // Wildcard - string a [*]; + string a [*] = '{default: "nope", "BBBBB": "fooing", 23'h434343: "baring"}; int k; string v; + v = a["CCC"]; `checks(v, "baring"); + v = a["BBBBB"]; `checks(v, "fooing"); + a[32'd1234] = "fooed"; a[4'd3] = "bared"; - i = a.num(); `checkh(i, 2); - i = a.size(); `checkh(i, 2); - v = a[32'd1234]; `checks(v, "fooed"); + a[79'h4141] = "bazed"; + i = a.num(); `checkh(i, 5); + i = a.size(); `checkh(i, 5); + v = a[39'd1234]; `checks(v, "fooed"); + v = a["AA"]; `checks(v, "bazed"); v = a[4'd3]; `checks(v, "bared"); i = a.exists("baz"); `checkh(i, 0); i = a.exists(4'd3); `checkh(i, 1); - i = a.first(k); `checkh(i, 1); `checks(k, 4'd3); - i = a.next(k); `checkh(i, 1); `checks(k, 32'd1234); - i = a.next(k); `checkh(i, 0); - i = a.last(k); `checkh(i, 1); `checks(k, 32'd1234); - i = a.prev(k); `checkh(i, 1); `checks(k, 4'd3); - i = a.prev(k); `checkh(i, 0); a.delete(4'd3); - i = a.size(); `checkh(i, 1); + i = a.size(); `checkh(i, 4); end $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_assoc_wildcard_bad.out b/test_regress/t/t_assoc_wildcard_bad.out new file mode 100644 index 000000000..2bd459ff6 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_bad.out @@ -0,0 +1,73 @@ +%Error: t/t_assoc_wildcard_bad.v:23:13: The 1 arguments passed to .num method does not match its requiring 0 arguments + : ... In instance t + 23 | v = a.num("badarg"); + | ^~~ +%Error: t/t_assoc_wildcard_bad.v:24:13: The 1 arguments passed to .size method does not match its requiring 0 arguments + : ... In instance t + 24 | v = a.size("badarg"); + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:25:13: The 0 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + 25 | v = a.exists(); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:26:13: The 2 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + 26 | v = a.exists(k, "bad2"); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:27:9: The 2 arguments passed to .delete method does not match its requiring 0 to 1 arguments + : ... In instance t + 27 | a.delete(k, "bad2"); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:29:9: Array method 'sort' not legal on associative arrays + : ... In instance t + 29 | a.sort; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:30:9: Array method 'rsort' not legal on associative arrays + : ... In instance t + 30 | a.rsort; + | ^~~~~ +%Error: t/t_assoc_wildcard_bad.v:31:9: Array method 'reverse' not legal on associative arrays + : ... In instance t + 31 | a.reverse; + | ^~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:32:9: Array method 'shuffle' not legal on associative arrays + : ... In instance t + 32 | a.shuffle; + | ^~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:34:9: Array method 'first' not legal on wildcard associative arrays + : ... In instance t + 34 | a.first; + | ^~~~~ +%Error: t/t_assoc_wildcard_bad.v:35:9: Array method 'last' not legal on wildcard associative arrays + : ... In instance t + 35 | a.last; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:36:9: Array method 'next' not legal on wildcard associative arrays + : ... In instance t + 36 | a.next; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:37:9: Array method 'prev' not legal on wildcard associative arrays + : ... In instance t + 37 | a.prev; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:38:9: Array method 'unique_index' not legal on wildcard associative arrays + : ... In instance t + 38 | a.unique_index; + | ^~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:39:9: Array method 'find_index' not legal on wildcard associative arrays + : ... In instance t + 39 | a.find_index; + | ^~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:40:9: Array method 'find_first_index' not legal on wildcard associative arrays + : ... In instance t + 40 | a.find_first_index; + | ^~~~~~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:41:9: Array method 'find_last_index' not legal on wildcard associative arrays + : ... In instance t + 41 | a.find_last_index; + | ^~~~~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:43:8: Wildcard index must be integral (IEEE 1800-2017 7.8.1) + : ... In instance t + 43 | a[x] = "bad"; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_wildcard_unsup.pl b/test_regress/t/t_assoc_wildcard_bad.pl similarity index 100% rename from test_regress/t/t_assoc_wildcard_unsup.pl rename to test_regress/t/t_assoc_wildcard_bad.pl diff --git a/test_regress/t/t_assoc_wildcard_bad.v b/test_regress/t/t_assoc_wildcard_bad.v new file mode 100644 index 000000000..85ebf83ba --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_bad.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +typedef class Cls; + +class Cls; + integer imembera; + integer imemberb; +endclass : Cls + +module t (/*AUTOARG*/); + + initial begin + string a [*]; + string k; + string v; + + Cls x; + + v = a.num("badarg"); + v = a.size("badarg"); + v = a.exists(); // Bad + v = a.exists(k, "bad2"); + a.delete(k, "bad2"); + + a.sort; // Not legal on assoc + a.rsort; // Not legal on assoc + a.reverse; // Not legal on assoc + a.shuffle; // Not legal on assoc + + a.first; // Not legal on wildcard + a.last; // Not legal on wildcard + a.next; // Not legal on wildcard + a.prev; // Not legal on wildcard + a.unique_index; // Not legal on wildcard + a.find_index; // Not legal on wildcard + a.find_first_index; // Not legal on wildcard + a.find_last_index; // Not legal on wildcard + + a[x] = "bad"; + end +endmodule diff --git a/test_regress/t/t_assoc_wildcard_method.pl b/test_regress/t/t_assoc_wildcard_method.pl new file mode 100755 index 000000000..b46d46042 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_method.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_wildcard_method.v b/test_regress/t/t_assoc_wildcard_method.v new file mode 100644 index 000000000..c61086e7b --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_method.v @@ -0,0 +1,127 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); + +module t (/*AUTOARG*/); + initial begin + int q[*]; + int qe[*]; // Empty + int qv[$]; // Value returns + int qi[$]; // Index returns + int i; + string v; + + q = '{"a":1, "b":2, "c":2, "d":4, "e":3}; + v = $sformatf("%p", q); `checks(v, "'{\"a\":'h1, \"b\":'h2, \"c\":'h2, \"d\":'h4, \"e\":'h3} "); + + // NOT tested: with ... selectors + + //q.sort; // Not legal on assoc - see t_assoc_meth_bad + //q.rsort; // Not legal on assoc - see t_assoc_meth_bad + //q.reverse; // Not legal on assoc - see t_assoc_meth_bad + //q.shuffle; // Not legal on assoc - see t_assoc_meth_bad + + v = $sformatf("%p", qe); `checks(v, "'{}"); + qv = q.unique; + v = $sformatf("%p", qv); `checks(v, "'{'h1, 'h2, 'h4, 'h3} "); + qv = qe.unique; + v = $sformatf("%p", qv); `checks(v, "'{}"); + + //q.unique_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + + // These require an with clause or are illegal + qv = q.find with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2, 'h2} "); + qv = q.find_first with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2} "); + qv = q.find_last with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2} "); + + qv = q.find with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = q.find_first with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = q.find_last with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + + //q.find_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + //q.find_first_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + //q.find_last_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + + qv = q.min; + v = $sformatf("%p", qv); `checks(v, "'{'h1} "); + qv = q.max; + v = $sformatf("%p", qv); `checks(v, "'{'h4} "); + + qv = qe.min; + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = qe.max; + v = $sformatf("%p", qv); `checks(v, "'{}"); + + // Reduction methods + + i = q.sum; + `checkh(i, 32'hc); + i = q.sum with (item + 1); + `checkh(i, 32'h11); + i = q.product; + `checkh(i, 32'h30); + i = q.product with (item + 1); + `checkh(i, 32'h168); + + i = qe.sum; + `checkh(i, 32'h0); + i = qe.product; + `checkh(i, 32'h0); + + q = '{10:32'b1100, 11:32'b1010}; + i = q.and; + `checkh(i, 32'b1000); + i = q.and with (item + 1); + `checkh(i, 32'b1001); + i = q.or; + `checkh(i, 32'b1110); + i = q.or with (item + 1); + `checkh(i, 32'b1111); + i = q.xor; + `checkh(i, 32'b0110); + i = q.xor with (item + 1); + `checkh(i, 32'b0110); + + i = qe.and; + `checkh(i, 32'b0); + i = qe.or; + `checkh(i, 32'b0); + i = qe.xor; + `checkh(i, 32'b0); + + i = q.and(); + `checkh(i, 32'b1000); + i = q.and() with (item + 1); + `checkh(i, 32'b1001); + i = q.or(); + `checkh(i, 32'b1110); + i = q.or() with (item + 1); + `checkh(i, 32'b1111); + i = q.xor(); + `checkh(i, 32'b0110); + i = q.xor() with (item + 1); + `checkh(i, 32'b0110); + + i = qe.and(); + `checkh(i, 32'b0); + i = qe.or(); + `checkh(i, 32'b0); + i = qe.xor(); + `checkh(i, 32'b0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assoc_wildcard_unsup.out b/test_regress/t/t_assoc_wildcard_unsup.out deleted file mode 100644 index 209295f7f..000000000 --- a/test_regress/t/t_assoc_wildcard_unsup.out +++ /dev/null @@ -1,5 +0,0 @@ -%Error-UNSUPPORTED: t/t_assoc_wildcard_unsup.v:25:19: Unsupported: [*] wildcard associative arrays - 25 | string a [*]; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to