From b81295230a866e38c418349d9ed56f3a42424523 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 1 Dec 2019 11:52:48 -0500 Subject: [PATCH] Support associative arrays. --- Changes | 2 + include/verilated.cpp | 13 ++ include/verilated_heavy.h | 150 ++++++++++++++++++ include/verilated_save.h | 29 ++++ src/V3Ast.cpp | 4 + src/V3Ast.h | 4 + src/V3AstNodes.cpp | 52 ++++++- src/V3AstNodes.h | 179 +++++++++++++++++++++- src/V3Clean.cpp | 9 +- src/V3Dead.cpp | 3 + src/V3EmitC.cpp | 47 +++++- src/V3EmitCInlines.cpp | 4 + src/V3ParseGrammar.cpp | 5 + src/V3Premit.cpp | 10 ++ src/V3Reloop.cpp | 1 + src/V3Width.cpp | 124 ++++++++++++++- src/V3WidthCommit.h | 1 + src/V3WidthSel.cpp | 11 ++ src/verilog.y | 6 +- test_regress/t/t_assoc.pl | 20 +++ test_regress/t/t_assoc.v | 104 +++++++++++++ test_regress/t/t_assoc_meth_bad.out | 37 +++++ test_regress/t/t_assoc_meth_bad.pl | 18 +++ test_regress/t/t_assoc_meth_bad.v | 23 +++ test_regress/t/t_assoc_pattern_unsup.out | 5 + test_regress/t/t_assoc_pattern_unsup.pl | 18 +++ test_regress/t/t_assoc_pattern_unsup.v | 27 ++++ test_regress/t/t_assoc_wildcard_unsup.out | 2 + test_regress/t/t_assoc_wildcard_unsup.pl | 18 +++ test_regress/t/t_assoc_wildcard_unsup.v | 49 ++++++ test_regress/t/t_savable.v | 7 +- test_regress/t/t_trace_complex.v | 3 + 32 files changed, 967 insertions(+), 18 deletions(-) create mode 100755 test_regress/t/t_assoc.pl create mode 100644 test_regress/t/t_assoc.v create mode 100644 test_regress/t/t_assoc_meth_bad.out create mode 100755 test_regress/t/t_assoc_meth_bad.pl create mode 100644 test_regress/t/t_assoc_meth_bad.v create mode 100644 test_regress/t/t_assoc_pattern_unsup.out create mode 100755 test_regress/t/t_assoc_pattern_unsup.pl create mode 100644 test_regress/t/t_assoc_pattern_unsup.v create mode 100644 test_regress/t/t_assoc_wildcard_unsup.out create mode 100755 test_regress/t/t_assoc_wildcard_unsup.pl create mode 100644 test_regress/t/t_assoc_wildcard_unsup.v diff --git a/Changes b/Changes index 233703046..2ba870128 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,8 @@ The contributors that suggested a given feature are shown in []. Thanks! * Verilator 4.023 devel +** Support associative arrays (excluding [*] and pattern assignments). + *** Add +verilator+error+limit to see more assertion errors. [Peter Monsson] *** Support string.toupper and string.tolower. diff --git a/include/verilated.cpp b/include/verilated.cpp index 21dff74e5..28ea5029e 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1758,6 +1758,19 @@ const char* vl_mc_scan_plusargs(const char* prefixp) VL_MT_SAFE { //=========================================================================== // Heavy string functions +std::string VL_TO_STRING(CData lhs) { + return VL_SFORMATF_NX("'h%0x", 8, lhs); +} +std::string VL_TO_STRING(SData lhs) { + return VL_SFORMATF_NX("'h%0x", 16, lhs); +} +std::string VL_TO_STRING(IData lhs) { + return VL_SFORMATF_NX("'h%0x", 32, lhs); +} +std::string VL_TO_STRING(QData lhs) { + return VL_SFORMATF_NX("'h%0x", 64, lhs); +} + std::string VL_TOLOWER_NN(const std::string& ld) VL_MT_SAFE { std::string out = ld; for (std::string::iterator it = out.begin(); it != out.end(); ++it) { diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index 7e0e9dd7a..0b5c9201e 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -30,8 +30,158 @@ #include "verilated.h" +//IFDEF C11 +#include + +#include #include +//=================================================================== +// String formatters (required by below containers) + +extern std::string VL_TO_STRING(CData obj); +extern std::string VL_TO_STRING(SData obj); +extern std::string VL_TO_STRING(IData obj); +extern std::string VL_TO_STRING(QData obj); +inline std::string VL_TO_STRING(const std::string& obj) { return "\""+obj+"\""; } + +//=================================================================== +// Verilog array container +// Similar to std::array, but: +// 1. Doesn't require C++11 +// 2. Lighter weight, only methods needed by Verilator, to help compile time. +// +// This is only used when we need an upper-level container and so can't +// simply use a C style array (which is just a pointer). + +template class VlWide { + WData m_storage[T_Words]; +public: + // Default constructor/destructor/copy are fine + const WData& at(size_t index) const { return m_storage[index]; } + WData& at(size_t index) { return m_storage[index]; } + WData* data() { return &m_storage[0]; } + const WData* data() const { return &m_storage[0]; } + bool operator<(const VlWide& rhs) const { + return VL_LT_W(T_Words, data(), rhs.data()); + } +}; + +// Convert a C array to std::array reference by pointer magic, without copy. +// Data type (second argument) is so the function template can automatically generate. +template +VlWide& VL_CVT_W_A(WDataInP inp, const VlWide&) { + return *((VlWide*)inp); +} + + +//=================================================================== +// Verilog associative array container +// There are no multithreaded locks on this; the base variable must +// be protected by other means +// +template class VlAssocArray { +private: + // TYPES + typedef std::map Map; +public: + typedef typename Map::const_iterator const_iterator; + +private: + // MEMBERS + Map m_map; // State of the assoc array + T_Value m_defaultValue; // Default value + +public: + // CONSTRUCTORS + VlAssocArray() { + // m_defaultValue isn't defaulted. Caller's constructor must do it. + } + ~VlAssocArray() {} + // Standard copy constructor works. Verilog: assoca = assocb + + // METHODS + T_Value& atDefault() { return m_defaultValue; } + + // Size of array. Verilog: function int size(), or int num() + int size() const { return m_map.size(); } + // Clear array. Verilog: function void delete([input index]) + void clear() { m_map.clear(); } + void erase(const T_Key& index) { m_map.erase(index); } + // Return 0/1 if element exists. Verilog: function int exists(input index) + int exists(const T_Key& index) const { return m_map.find(index) != m_map.end(); } + // Return first element. Verilog: function int first(ref index); + int first(T_Key& indexr) const { + typename Map::const_iterator it = m_map.begin(); + if (it == m_map.end()) return 0; + indexr = it->first; + return 1; + } + // Return last element. Verilog: function int last(ref index) + int last(T_Key& indexr) const { + typename Map::const_reverse_iterator it = m_map.rbegin(); + if (it == m_map.rend()) return 0; + indexr = it->first; + return 1; + } + // Return next element. Verilog: function int next(ref index) + int next(T_Key& indexr) const { + typename Map::const_iterator it = m_map.find(indexr); + if (VL_UNLIKELY(it == m_map.end())) return 0; + it++; + if (VL_UNLIKELY(it == m_map.end())) return 0; + indexr = it->first; + return 1; + } + // Return prev element. Verilog: function int prev(ref index) + int prev(T_Key& indexr) const { + typename Map::const_iterator it = m_map.find(indexr); + if (VL_UNLIKELY(it == m_map.end())) return 0; + if (VL_UNLIKELY(it == m_map.begin())) return 0; + --it; + indexr = it->first; + return 1; + } + // Setting. Verilog: assoc[index] = v + // Can't just overload operator[] or provide a "at" reference to set, + // because we need to be able to insert only when the value is set + T_Value& at(const T_Key& index) { + typename Map::iterator it = m_map.find(index); + if (it == m_map.end()) { + std::pair pit + = m_map.insert(std::make_pair(index, m_defaultValue)); + return pit.first->second; + } + return it->second; + } + // Accessing. Verilog: v = assoc[index] + const T_Value& at(const T_Key& index) const { + typename Map::iterator it = m_map.find(index); + if (it == m_map.end()) return m_defaultValue; + else return it->second; + } + // For save/restore + const_iterator begin() const { return m_map.begin(); } + const_iterator end() const { return m_map.end(); } + + // Dumping. Verilog: str = $sformatf("%p", assoc) + std::string to_string() const { + std::string out = "'{"; + std::string comma; + for (typename Map::const_iterator it = m_map.begin(); it != m_map.end(); ++it) { + out += comma + VL_TO_STRING(it->first) + ":" + VL_TO_STRING(it->second); + comma = ", "; + } + // Default not printed - maybe random init data + return out + "} "; + } +}; + +template +std::string VL_TO_STRING(const VlAssocArray& obj) { + return obj.to_string(); +} + //====================================================================== // Conversion functions diff --git a/include/verilated_save.h b/include/verilated_save.h index fe1a2978c..47bd03df3 100644 --- a/include/verilated_save.h +++ b/include/verilated_save.h @@ -23,6 +23,7 @@ #define _VERILATED_SAVE_C_H_ 1 #include "verilatedos.h" +#include "verilated_heavy.h" #include @@ -247,5 +248,33 @@ inline VerilatedDeserialize& operator>>(VerilatedDeserialize& os, std::string& r rhs.resize(len); return os.read((void*)rhs.data(), len); } +template +VerilatedSerialize& operator<<(VerilatedSerialize& os, VlAssocArray& rhs) { + os << rhs.atDefault(); + vluint32_t len = rhs.size(); + os << len; + for (typename VlAssocArray::const_iterator it = rhs.begin(); + it != rhs.end(); ++it) { + T_Key index = it->first; // Copy to get around const_iterator + T_Value value = it->second; + os << index << value; + } + return os; +} +template +VerilatedDeserialize& operator>>(VerilatedDeserialize& os, VlAssocArray& rhs) { + os >> rhs.atDefault(); + vluint32_t len = 0; + os >> len; + rhs.clear(); + for (vluint32_t i = 0; i < len; ++i) { + T_Key index; + T_Value value; + os >> index; + os >> value; + rhs.at(index) = value; + } + return os; +} #endif // Guard diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 7c3ffe108..d64fda835 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1199,6 +1199,10 @@ AstBasicDType* AstNode::findInsertSameDType(AstBasicDType* nodep) { return v3Global.rootp()->typeTablep() ->findInsertSameDType(nodep); } +AstNodeDType* AstNode::findVoidDType() const { + return v3Global.rootp()->typeTablep() + ->findVoidDType(fileline()); +} //###################################################################### // AstNVisitor diff --git a/src/V3Ast.h b/src/V3Ast.h index 37ebde6b7..77b092e4c 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1421,6 +1421,7 @@ public: void dtypeSetSigned32() { dtypep(findSigned32DType()); } void dtypeSetUInt32() { dtypep(findUInt32DType()); } // Twostate void dtypeSetUInt64() { dtypep(findUInt64DType()); } // Twostate + void dtypeSetVoid() { dtypep(findVoidDType()); } // Data type locators AstNodeDType* findLogicBoolDType() { return findBasicDType(AstBasicDTypeKwd::LOGIC); } @@ -1429,6 +1430,7 @@ public: AstNodeDType* findSigned32DType() { return findBasicDType(AstBasicDTypeKwd::INTEGER); } AstNodeDType* findUInt32DType() { return findBasicDType(AstBasicDTypeKwd::UINT32); } // Twostate AstNodeDType* findUInt64DType() { return findBasicDType(AstBasicDTypeKwd::UINT64); } // Twostate + AstNodeDType* findVoidDType() const; AstNodeDType* findBitDType(int width, int widthMin, AstNumeric numeric) const; AstNodeDType* findLogicDType(int width, int widthMin, AstNumeric numeric) const; AstNodeDType* findLogicRangeDType(VNumRange range, int widthMin, AstNumeric numeric) const; @@ -1914,6 +1916,8 @@ public: virtual bool maybePointedTo() const { return true; } virtual AstNodeDType* virtRefDTypep() const { return NULL; } // Iff has a non-null refDTypep(), as generic node function virtual void virtRefDTypep(AstNodeDType* nodep) { } // Iff has refDTypep(), set as generic node function + virtual AstNodeDType* virtRefDType2p() const { return NULL; } // Iff has a non-null second dtypep, as generic node function + virtual void virtRefDType2p(AstNodeDType* nodep) { } // Iff has second dtype, set as generic node function virtual bool similarDType(AstNodeDType* samep) const = 0; // Assignable equivalence. Call skipRefp() on this and samep before calling virtual AstNodeDType* subDTypep() const { return NULL; } // Iff has a non-null subDTypep(), as generic node function virtual bool isFourstate() const; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 255265aff..222d1f059 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -247,7 +247,7 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& string ostatic; if (isStatic() && namespc.empty()) ostatic = "static "; - VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, dtypep()); + VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, dtypep(), false); string oname; if (named) { @@ -258,11 +258,28 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& return ostatic + info.m_oprefix + info.refParen(oname) + info.m_osuffix; } -AstVar::VlArgTypeRecursed AstVar::vlArgTypeRecurse(bool forFunc, - const AstNodeDType* dtypep) const { +AstVar::VlArgTypeRecursed AstVar::vlArgTypeRecurse(bool forFunc, const AstNodeDType* dtypep, + bool arrayed) const { dtypep = dtypep->skipRefp(); - if (const AstUnpackArrayDType* adtypep = VN_CAST_CONST(dtypep, UnpackArrayDType)) { - VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, adtypep->subDTypep()); + if (const AstAssocArrayDType* adtypep = VN_CAST_CONST(dtypep, AssocArrayDType)) { + VlArgTypeRecursed key = vlArgTypeRecurse(forFunc, adtypep->keyDTypep(), true); + VlArgTypeRecursed sub = vlArgTypeRecurse(forFunc, adtypep->subDTypep(), true); + string out = "VlAssocArray<"; + out += key.m_oprefix; + if (!key.m_osuffix.empty() || !key.m_oref.empty()) { + out += " " + key.m_osuffix + key.m_oref; + } + out += ", "; + out += sub.m_oprefix; + if (!sub.m_osuffix.empty() || !sub.m_oref.empty()) { + out += " " + sub.m_osuffix + sub.m_oref; + } + out += ">"; + VlArgTypeRecursed info; + info.m_oprefix = out; + return info; + } else if (const AstUnpackArrayDType* adtypep = VN_CAST_CONST(dtypep, UnpackArrayDType)) { + VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, adtypep->subDTypep(), arrayed); info.m_osuffix = "[" + cvtToStr(adtypep->declRange().elements()) + "]" + info.m_osuffix; return info; } else if (const AstBasicDType* bdtypep = dtypep->basicp()) { @@ -297,8 +314,12 @@ AstVar::VlArgTypeRecursed AstVar::vlArgTypeRecurse(bool forFunc, } else if (dtypep->isQuad()) { otype += "QData"+bitvec; } else if (dtypep->isWide()) { - otype += "WData"+bitvec; // []'s added later - oarray += "["+cvtToStr(dtypep->widthWords())+"]"; + if (arrayed) { + otype += "VlWide<"+cvtToStr(dtypep->widthWords())+">"; + } else { + otype += "WData"+bitvec; // []'s added later + oarray += "["+cvtToStr(dtypep->widthWords())+"]"; + } } string oref; @@ -740,6 +761,15 @@ void AstTypeTable::repairCache() { } } +AstVoidDType* AstTypeTable::findVoidDType(FileLine* fl) { + if (VL_UNLIKELY(!m_voidp)) { + AstVoidDType* newp = new AstVoidDType(fl); + addTypesp(newp); + m_voidp = newp; + } + return m_voidp; +} + AstBasicDType* AstTypeTable::findBasicDType(FileLine* fl, AstBasicDTypeKwd kwd) { if (m_basicps[kwd]) return m_basicps[kwd]; // @@ -1075,10 +1105,18 @@ void AstTypeTable::dump(std::ostream& str) const { } // Note get newline from caller too. } +void AstAssocArrayDType::dumpSmall(std::ostream& str) const { + this->AstNodeDType::dumpSmall(str); + str<<"[assoc-"<<(void*)keyDTypep()<<"]"; +} void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const { this->AstNodeDType::dumpSmall(str); str<<"[]"; } +void AstVoidDType::dumpSmall(std::ostream& str) const { + this->AstNodeDType::dumpSmall(str); + str<<"void"; +} void AstVarScope::dump(std::ostream& str) const { this->AstNode::dump(str); if (isCircular()) str<<" [CIRC]"; diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 6ad46e7a8..e1623f3d5 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -184,6 +184,22 @@ public: virtual bool same(const AstNode* samep) const { return true; } }; +class AstAssocRange : public AstNodeRange { + // Associative array range specification + // Only for early parsing - becomes AstAssocDType +public: + explicit AstAssocRange(FileLine* fl, AstNodeDType* dtp) + : AstNodeRange(fl) { + setOp1p(dtp); + } + ASTNODE_NODE_FUNCS(AssocRange) + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitVerilog() { V3ERROR_NA; return ""; } + virtual V3Hash sameHash() const { return V3Hash(); } + virtual bool same(const AstNode* samep) const { return true; } + AstNodeDType* keyDTypep() const { return VN_CAST(op1p(), NodeDType); } +}; + class AstUnsizedRange : public AstNodeRange { // Unsized range specification, for open arrays public: @@ -333,6 +349,65 @@ public: void name(const string& flag) { m_name = flag; } }; +class AstAssocArrayDType : public AstNodeDType { + // Associative array data type, ie "[some_dtype]" + // Children: DTYPE (moved to refDTypep() in V3Width) + // Children: DTYPE (the key, which remains here as a pointer) +private: + AstNodeDType* m_refDTypep; // Elements of this type (after widthing) + AstNodeDType* m_keyDTypep; // Keys of this type (after widthing) +public: + AstAssocArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstNodeDType* keyDtp) + : AstNodeDType(fl) { + childDTypep(dtp); // Only for parser + keyChildDTypep(keyDtp); // Only for parser + refDTypep(NULL); + keyDTypep(NULL); + dtypep(NULL); // V3Width will resolve + } + ASTNODE_NODE_FUNCS(AssocArrayDType) + virtual const char* broken() const { + BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) + || (!m_refDTypep && childDTypep()))); + BROKEN_RTN(!((m_keyDTypep && !childDTypep() && m_keyDTypep->brokeExists()) + || (!m_keyDTypep && childDTypep()))); + return NULL; } + virtual void cloneRelink() { + if (m_refDTypep && m_refDTypep->clonep()) { m_refDTypep = m_refDTypep->clonep(); } + if (m_keyDTypep && m_keyDTypep->clonep()) { m_keyDTypep = m_keyDTypep->clonep(); } } + virtual bool same(const AstNode* samep) const { + const AstAssocArrayDType* asamep = static_cast(samep); + return (subDTypep() == asamep->subDTypep() + && keyDTypep() == asamep->keyDTypep()); } + virtual bool similarDType(AstNodeDType* samep) const { + const AstAssocArrayDType* asamep = static_cast(samep); + return (subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp())); + } + virtual void dumpSmall(std::ostream& str) const; + virtual V3Hash sameHash() const { return V3Hash(m_refDTypep); } + AstNodeDType* getChildDTypep() const { return childDTypep(); } + AstNodeDType* childDTypep() const { return VN_CAST(op1p(), NodeDType); } // op1 = Range of variable + void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } + virtual AstNodeDType* subDTypep() const { return m_refDTypep ? m_refDTypep : childDTypep(); } + void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } + virtual AstNodeDType* virtRefDTypep() const { return m_refDTypep; } + virtual void virtRefDTypep(AstNodeDType* nodep) { refDTypep(nodep); } + virtual AstNodeDType* virtRefDType2p() const { return m_keyDTypep; } + virtual void virtRefDType2p(AstNodeDType* nodep) { keyDTypep(nodep); } + // + AstNodeDType* keyDTypep() const { return m_keyDTypep ? m_keyDTypep : keyChildDTypep(); } + void keyDTypep(AstNodeDType* nodep) { m_keyDTypep = nodep; } + AstNodeDType* keyChildDTypep() const { return VN_CAST(op2p(), NodeDType); } // op1 = Range of variable + void keyChildDTypep(AstNodeDType* nodep) { setOp2p(nodep); } + // METHODS + virtual AstBasicDType* basicp() const { return NULL; } // (Slow) recurse down to find basic data type + virtual AstNodeDType* skipRefp() const { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToConstp() const { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToEnump() const { return (AstNodeDType*)this; } + virtual int widthAlignBytes() const { return subDTypep()->widthAlignBytes(); } + virtual int widthTotalBytes() const { return subDTypep()->widthTotalBytes(); } +}; + class AstPackArrayDType : public AstNodeArrayDType { // Packed array data type, ie "some_dtype [2:0] var_name" // Children: DTYPE (moved to refDTypep() in V3Width) @@ -774,6 +849,28 @@ public: void lsb(int lsb) { m_lsb = lsb; } }; +class AstVoidDType : public AstNodeDType { + // For e.g. a function returning void +public: + AstVoidDType(FileLine* fl) + : AstNodeDType(fl) { dtypep(this); } + ASTNODE_NODE_FUNCS(VoidDType) + virtual void dumpSmall(std::ostream& str) const; + virtual bool hasDType() const { return true; } + virtual bool maybePointedTo() const { return true; } + virtual AstNodeDType* subDTypep() const { return NULL; } + virtual AstNodeDType* virtRefDTypep() const { return NULL; } + virtual void virtRefDTypep(AstNodeDType* nodep) { } + virtual bool similarDType(AstNodeDType* samep) const { return this==samep; } + virtual AstBasicDType* basicp() const { return NULL; } + virtual AstNodeDType* skipRefp() const { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToConstp() const { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToEnump() const { return (AstNodeDType*)this; } + virtual int widthAlignBytes() const { return 1; } + virtual int widthTotalBytes() const { return 1; } + virtual V3Hash sameHash() const { return V3Hash(); } +}; + class AstEnumItem : public AstNode { private: string m_name; @@ -927,6 +1024,40 @@ public: static AstNode* baseFromp(AstNode* nodep); ///< What is the base variable (or const) this dereferences? }; +class AstAssocSel : public AstNodeSel { + // Parents: math|stmt + // Children: varref|arraysel, math +private: + void init(AstNode* fromp) { + if (fromp && VN_IS(fromp->dtypep()->skipRefp(), AssocArrayDType)) { + // Strip off array to find what array references + dtypeFrom(VN_CAST(fromp->dtypep()->skipRefp(), AssocArrayDType)->subDTypep()); + } + } +public: + AstAssocSel(FileLine* fl, AstNode* fromp, AstNode* bitp) + : AstNodeSel(fl, fromp, bitp) { + init(fromp); + } + ASTNODE_NODE_FUNCS(AssocSel) + virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) { + return new AstAssocSel(this->fileline(), lhsp, rhsp); } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { + V3ERROR_NA; } + virtual string emitVerilog() { return "%k(%l%f[%r])"; } + virtual string emitC() { return "%li%k[%ri]"; } + virtual bool cleanOut() const { return true; } + virtual bool cleanLhs() const { return false; } + virtual bool cleanRhs() const { return true; } + virtual bool sizeMattersLhs() const { return false; } + virtual bool sizeMattersRhs() const { return false; } + virtual bool isGateOptimizable() const { return true; } // esp for V3Const::ifSameAssign + virtual bool isPredictOptimizable() const { return false; } + virtual V3Hash sameHash() const { return V3Hash(); } + virtual bool same(const AstNode* samep) const { return true; } + virtual int instrCount() const { return widthInstrs(); } +}; + class AstWordSel : public AstNodeSel { // Select a single word from a multi-word wide value public: @@ -1119,7 +1250,7 @@ class AstMethodCall : public AstNode { // We do not support generic member calls yet, so this is only enough to // make built-in methods work private: - string m_name; // Name of variable + string m_name; // Name of method public: AstMethodCall(FileLine* fl, AstNode* fromp, VFlagChildDType, const string& name, AstNode* pinsp) : AstNode(fl), m_name(name) { @@ -1141,6 +1272,45 @@ public: void addPinsp(AstNode* nodep) { addOp2p(nodep); } }; +class AstCMethodCall : public AstNodeStmt { + // A reference to a "C" hardocded member task (or function) + // PARENTS: stmt/math + // Not all calls are statments vs math. AstNodeStmt needs isStatement() to deal. +private: + string m_name; // Name of method + bool m_pure; // Pure optimizable + bool m_statement; // Is a statement (AstNodeMath-like) versus AstNodeStmt-like +public: + AstCMethodCall(FileLine* fl, AstNode* fromp, VFlagChildDType, const string& name, + AstNode* pinsp) + : AstNodeStmt(fl), m_name(name), m_pure(false), m_statement(false) { + setOp1p(fromp); + dtypep(NULL); // V3Width will resolve + addNOp2p(pinsp); + } + AstCMethodCall(FileLine* fl, AstNode* fromp, const string& name, AstNode* pinsp) + : AstNodeStmt(fl), m_name(name), m_statement(false) { + setOp1p(fromp); + addNOp2p(pinsp); + } + ASTNODE_NODE_FUNCS(CMethodCall) + virtual string name() const { return m_name; } // * = Var name + virtual bool hasDType() const { return true; } + virtual void name(const string& name) { m_name = name; } + virtual V3Hash sameHash() const { return V3Hash(m_name); } + virtual bool same(const AstNode* samep) const { + const AstCMethodCall* asamep = static_cast(samep); + return (m_name == asamep->m_name); } + virtual bool isStatement() const { return m_statement; } + virtual bool isPure() const { return m_pure; } + void pure(bool flag) { m_pure = flag; } + void makeStatement() { m_statement = true; dtypeSetVoid(); } + AstNode* fromp() const { return op1p(); } // op1 = Extracting what (NULL=TBD during parsing) + void fromp(AstNode* nodep) { setOp1p(nodep); } + AstNode* pinsp() const { return op2p(); } // op2 = Pin interconnection list + void addPinsp(AstNode* nodep) { addOp2p(nodep); } +}; + class AstVar : public AstNode { // A variable (in/out/wire/reg/param) inside a module private: @@ -1412,7 +1582,8 @@ public: string mtasksString() const; private: class VlArgTypeRecursed; - VlArgTypeRecursed vlArgTypeRecurse(bool forFunc, const AstNodeDType* dtypep) const; + VlArgTypeRecursed vlArgTypeRecurse(bool forFunc, const AstNodeDType* dtypep, + bool arrayed) const; }; class AstDefParam : public AstNode { @@ -6333,17 +6504,19 @@ public: class AstTypeTable : public AstNode { // Container for hash of standard data types // Children: NODEDTYPEs + AstVoidDType* m_voidp; AstBasicDType* m_basicps[AstBasicDTypeKwd::_ENUM_MAX]; // typedef std::map DetailedMap; DetailedMap m_detailedMap; public: - explicit AstTypeTable(FileLine* fl) : AstNode(fl) { + explicit AstTypeTable(FileLine* fl) : AstNode(fl), m_voidp(NULL) { for (int i=0; iuser2() && nodep->hasDType()) { if (VN_IS(nodep, Var) || VN_IS(nodep, NodeDType) // Don't want to change variable widths! - || VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)) { // Or arrays + || VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType) // Or arrays + || VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType) + || VN_IS(nodep->dtypep()->skipRefp(), VoidDType)) { } else { setCppWidth(nodep); } @@ -276,6 +278,11 @@ private: insureCleanAndNext(nodep->argsp()); setClean(nodep, true); } + virtual void visit(AstCMethodCall* nodep) { + iterateChildren(nodep); + insureCleanAndNext(nodep->pinsp()); + setClean(nodep, true); + } //-------------------- // Default: Just iterate diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index a8273ece3..d34b80221 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -127,6 +127,9 @@ private: if (AstNode* subnodep = nodep->virtRefDTypep()) { subnodep->user1Inc(); } + if (AstNode* subnodep = nodep->virtRefDType2p()) { + subnodep->user1Inc(); + } } // VISITORS diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 776fe920b..e6c616f74 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -191,7 +191,9 @@ public: } else if (nodep->isWide() && VN_IS(nodep->lhsp(), VarRef) && !VN_IS(nodep->rhsp(), CMath) + && !VN_IS(nodep->rhsp(), CMethodCall) && !VN_IS(nodep->rhsp(), VarRef) + && !VN_IS(nodep->rhsp(), AssocSel) && !VN_IS(nodep->rhsp(), ArraySel)) { // Wide functions assign into the array directly, don't need separate assign statement m_wideTempRefp = VN_CAST(nodep->lhsp(), VarRef); @@ -215,6 +217,27 @@ public: } virtual void visit(AstAlwaysPublic*) { } + virtual void visit(AstAssocSel* nodep) { + iterateAndNextNull(nodep->fromp()); + putbs(".at("); + AstAssocArrayDType* adtypep = VN_CAST(nodep->fromp()->dtypep(), AssocArrayDType); + UASSERT_OBJ(adtypep, nodep, "Associative select on non-associative type"); + if (adtypep->keyDTypep()->isWide()) { + // Container class must take non-C-array (pointer) argument, so convert + putbs("VL_CVT_W_A("); + iterateAndNextNull(nodep->bitp()); + puts(", "); + iterateAndNextNull(nodep->fromp()); + putbs(".atDefault()"); // Not accessed; only to get the proper type of values + puts(")"); + } else { + iterateAndNextNull(nodep->bitp()); + } + puts(")"); + if (nodep->dtypep()->isWide()) { + puts(".data()"); // Access returned std::array as C array + } + } virtual void visit(AstCCall* nodep) { puts(nodep->hiernameProtect()); puts(nodep->funcp()->nameProtect()); @@ -233,6 +256,23 @@ public: puts(");\n"); } } + virtual void visit(AstCMethodCall* nodep) { + iterate(nodep->fromp()); + puts("."); + puts(nodep->nameProtect()); + puts("("); + bool comma = false; + for (AstNode* subnodep = nodep->pinsp(); subnodep; subnodep = subnodep->nextp()) { + if (comma) puts(", "); + iterate(subnodep); + comma = true; + } + puts(")"); + // Some are statements some are math. + if (nodep->isStatement()) puts(";\n"); + UASSERT_OBJ(!nodep->isStatement() || VN_IS(nodep->dtypep(), VoidDType), + nodep, "Statement of non-void data type"); + } virtual void visit(AstNodeCase* nodep) { // In V3Case... nodep->v3fatalSrc("Case statements should have been reduced out"); @@ -1303,7 +1343,12 @@ class EmitCImp : EmitCStmts { dtypep = dtypep->skipRefp(); AstBasicDType* basicp = dtypep->basicp(); // Returns string to do resetting, empty to do nothing (which caller should handle) - if (AstUnpackArrayDType* adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + if (AstAssocArrayDType* adtypep = VN_CAST(dtypep, AssocArrayDType)) { + string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); // Access std::array as C array + return emitVarResetRecurse(varp, adtypep->subDTypep(), depth+1, + ".atDefault()" + cvtarray); + } + else if (AstUnpackArrayDType* adtypep = VN_CAST(dtypep, UnpackArrayDType)) { UASSERT_OBJ(adtypep->msb() >= adtypep->lsb(), varp, "Should have swapped msb & lsb earlier."); string ivar = string("__Vi")+cvtToStr(depth); diff --git a/src/V3EmitCInlines.cpp b/src/V3EmitCInlines.cpp index 11a397fc3..b78ebf15e 100644 --- a/src/V3EmitCInlines.cpp +++ b/src/V3EmitCInlines.cpp @@ -46,6 +46,10 @@ class EmitCInlines : EmitCBaseVisitor { v3Global.needHeavy(true); } } + virtual void visit(AstAssocArrayDType* nodep) { + v3Global.needHeavy(true); + iterateChildren(nodep); + } virtual void visit(AstValuePlusArgs* nodep) { v3Global.needHeavy(true); iterateChildren(nodep); diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index 264da33a4..871984ebb 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -122,6 +122,11 @@ AstNodeDType* V3ParseGrammar::createArray(AstNodeDType* basep, } else if (VN_IS(nrangep, UnsizedRange)) { arrayp = new AstUnsizedArrayDType (nrangep->fileline(), VFlagChildDType(), arrayp); + } else if (VN_IS(nrangep, AssocRange)) { + AstAssocRange* arangep = VN_CAST(nrangep, AssocRange); + AstNodeDType* keyp = arangep->keyDTypep(); keyp->unlinkFrBack(); + arrayp = new AstAssocArrayDType + (nrangep->fileline(), VFlagChildDType(), arrayp, keyp); } else { UASSERT_OBJ(0, nrangep, "Expected range or unsized range"); } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index cb16d3a48..532054324 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -349,6 +349,16 @@ private: } checkNode(nodep); } + virtual void visit(AstAssocSel* nodep) { + iterateAndNextNull(nodep->fromp()); + { // Only the 'from' is part of the assignment LHS + bool prevAssign = m_assignLhs; + m_assignLhs = false; + iterateAndNextNull(nodep->bitp()); + m_assignLhs = prevAssign; + } + checkNode(nodep); + } virtual void visit(AstConst* nodep) { iterateChildren(nodep); checkNode(nodep); } diff --git a/src/V3Reloop.cpp b/src/V3Reloop.cpp index fea5b3a19..04e1c0885 100644 --- a/src/V3Reloop.cpp +++ b/src/V3Reloop.cpp @@ -162,6 +162,7 @@ private: // Of a constant index AstConst* lbitp = VN_CAST(lselp->bitp(), Const); if (!lbitp) { mergeEnd(); return; } + if (lbitp->width() > 32) { mergeEnd(); return; } // Assoc arrays can do this uint32_t index = lbitp->toUInt(); // Of variable AstNodeVarRef* lvarrefp = VN_CAST(lselp->fromp(), NodeVarRef); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 806974c3b..a0fb7c3a1 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -737,6 +737,20 @@ private: } } + virtual void visit(AstAssocSel* nodep) { + // Signed/Real: Output type based on array-declared type; binary operator + if (m_vup->prelim()) { + AstNodeDType* fromDtp = nodep->fromp()->dtypep()->skipRefp(); + AstAssocArrayDType* adtypep = VN_CAST(fromDtp, AssocArrayDType); + if (!adtypep) { + UINFO(1," Related dtype: "<v3fatalSrc("Associative array reference is not to associative array"); + } + iterateCheckTyped(nodep, "Associative select", nodep->bitp(), adtypep->keyDTypep(), BOTH); + nodep->dtypeFrom(adtypep->subDTypep()); + } + } + virtual void visit(AstSliceSel* nodep) { // Always creates as output an unpacked array if (m_vup->prelim()) { @@ -1052,6 +1066,16 @@ private: } UINFO(4,"dtWidthed "<didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed + if (nodep->childDTypep()) nodep->refDTypep(moveChildDTypeEdit(nodep)); + if (nodep->keyChildDTypep()) nodep->keyDTypep(moveDTypeEdit(nodep, nodep->keyChildDTypep())); + // Iterate into subDTypep() to resolve that type and update pointer. + nodep->refDTypep(iterateEditDTypep(nodep, nodep->subDTypep())); + nodep->keyDTypep(iterateEditDTypep(nodep, nodep->keyDTypep())); + nodep->dtypep(nodep); // The array itself, not subDtype + UINFO(4,"dtWidthed "<didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed if (nodep->childDTypep()) nodep->refDTypep(moveChildDTypeEdit(nodep)); @@ -1589,6 +1613,7 @@ private: } } else if (VN_IS(fromDtp, EnumDType) + || VN_IS(fromDtp, AssocArrayDType) || VN_IS(fromDtp, BasicDType)) { // Method call on enum without following parenthesis, e.g. "ENUM.next" // Convert this into a method call, and let that visitor figure out what to do next @@ -1629,6 +1654,11 @@ private: } } + virtual void visit(AstCMethodCall* nodep) { + // Never created before V3Width, so no need to redo it + UASSERT_OBJ(nodep->dtypep(), nodep, "CMETHODCALLs should have already been sized"); + } + virtual void visit(AstMethodCall* nodep) { UINFO(5," METHODSEL "<didWidth()) return; @@ -1648,6 +1678,9 @@ private: if (AstEnumDType* adtypep = VN_CAST(fromDtp, EnumDType)) { methodCallEnum(nodep, adtypep); } + else if (AstAssocArrayDType* adtypep = VN_CAST(fromDtp, AssocArrayDType)) { + methodCallAssoc(nodep, adtypep); + } else if (AstUnpackArrayDType* adtypep = VN_CAST(fromDtp, UnpackArrayDType)) { methodCallUnpack(nodep, adtypep); } @@ -1765,6 +1798,83 @@ private: nodep->v3error("Unknown built-in enum method " << nodep->prettyNameQ()); } } + void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { + AstCMethodCall* newp = NULL; + if (nodep->name() == "num" // function int num() + || nodep->name() == "size") { + methodOkArguments(nodep, 0, 0); + newp = new AstCMethodCall(nodep->fileline(), + nodep->fromp()->unlinkFrBack(), + "size", NULL); // So don't need num() + newp->dtypeSetSigned32(); + newp->didWidth(true); + newp->protect(false); + } else if (nodep->name() == "first" // function int first(ref index) + || nodep->name() == "last" + || nodep->name() == "next" + || nodep->name() == "prev") { + methodOkArguments(nodep, 1, 1); + AstNode* index_exprp = methodCallAssocIndexExpr(nodep, adtypep); + newp = new AstCMethodCall(nodep->fileline(), + nodep->fromp()->unlinkFrBack(), + nodep->name(), // first/last/next/prev + index_exprp->unlinkFrBack()); + newp->dtypeSetSigned32(); + newp->protect(false); + newp->didWidth(true); + } else if (nodep->name() == "exists") { // function int exists(input index) + // IEEE really should have made this a "bit" return + methodOkArguments(nodep, 1, 1); + AstNode* index_exprp = methodCallAssocIndexExpr(nodep, adtypep); + newp = new AstCMethodCall(nodep->fileline(), + nodep->fromp()->unlinkFrBack(), + "exists", index_exprp->unlinkFrBack()); + newp->dtypeSetSigned32(); + newp->pure(true); + newp->protect(false); + newp->didWidth(true); + } else if (nodep->name() == "delete") { // function void delete([input integer index]) + methodOkArguments(nodep, 0, 1); + methodCallLValue(nodep, nodep->fromp(), true); + if (!nodep->pinsp()) { + newp = new AstCMethodCall(nodep->fileline(), + nodep->fromp()->unlinkFrBack(), + "clear", NULL); + newp->protect(false); + newp->makeStatement(); + } else { + AstNode* index_exprp = methodCallAssocIndexExpr(nodep, adtypep); + newp = new AstCMethodCall(nodep->fileline(), + nodep->fromp()->unlinkFrBack(), + "erase", + index_exprp->unlinkFrBack()); + newp->protect(false); + newp->makeStatement(); + } + } else { + nodep->v3error("Unknown built-in associative array method "<prettyNameQ()); + } + if (newp) { + newp->didWidth(true); + nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); + } + } + AstNode* methodCallAssocIndexExpr(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { + AstNode* index_exprp = VN_CAST(nodep->pinsp(), Arg)->exprp(); + iterateCheck(nodep, "index", index_exprp, CONTEXT, FINAL, + adtypep->keyDTypep(), EXTEND_EXP); + VL_DANGLING(index_exprp); // May have been edited + return VN_CAST(nodep->pinsp(), Arg)->exprp(); + } + void methodCallLValue(AstMethodCall* nodep, AstNode* childp, bool lvalue) { + AstNodeVarRef* varrefp = VN_CAST(childp, NodeVarRef); + if (!varrefp) { + nodep->v3error("Unsupported: Non-variable on LHS of built-in method '" + <prettyName()<<"'"); + } else { + if (lvalue) varrefp->lvalue(true); + } + } void methodCallUnpack(AstMethodCall* nodep, AstUnpackArrayDType* adtypep) { enum { UNKNOWN = 0, ARRAY_OR, ARRAY_AND, ARRAY_XOR } methodId; @@ -2314,13 +2424,25 @@ private: break; } case 'p': { // Packed - AstBasicDType* basicp = argp ? argp->dtypep()->basicp() : NULL; + AstNodeDType* dtypep = argp ? argp->dtypep()->skipRefp() : NULL; + AstBasicDType* basicp = dtypep ? dtypep->basicp() : NULL; if (basicp && basicp->isString()) { added = true; newFormat += "\"%@\""; } else if (basicp && basicp->isDouble()) { added = true; newFormat += "%g"; + } else if (VN_IS(dtypep, AssocArrayDType)) { + added = true; + newFormat += "%@"; + AstNRelinker handle; + argp->unlinkFrBack(&handle); + AstCMethodCall* newp = new AstCMethodCall( + nodep->fileline(), argp, "to_string", NULL); + newp->dtypeSetString(); + newp->pure(true); + newp->protect(false); + handle.relink(newp); } else { added = true; if (fmt == "%0") newFormat += "'h%0h"; // IEEE our choice diff --git a/src/V3WidthCommit.h b/src/V3WidthCommit.h index 198610142..c92170cca 100644 --- a/src/V3WidthCommit.h +++ b/src/V3WidthCommit.h @@ -151,6 +151,7 @@ private: if (nodep->numeric().isNosign()) nodep->numeric(AstNumeric::UNSIGNED); iterateChildren(nodep); nodep->virtRefDTypep(editOneDType(nodep->virtRefDTypep())); + nodep->virtRefDType2p(editOneDType(nodep->virtRefDType2p())); } virtual void visit(AstNodePreSel* nodep) { // LCOV_EXCL_LINE // This check could go anywhere after V3Param diff --git a/src/V3WidthSel.cpp b/src/V3WidthSel.cpp index 86d399432..05700bd84 100644 --- a/src/V3WidthSel.cpp +++ b/src/V3WidthSel.cpp @@ -91,6 +91,8 @@ private: if (const AstNodeArrayDType* adtypep = VN_CAST(ddtypep, NodeArrayDType)) { fromRange = adtypep->declRange(); } + else if (const AstAssocArrayDType* adtypep = VN_CAST(ddtypep, AssocArrayDType)) { + } else if (const AstNodeClassDType* adtypep = VN_CAST(ddtypep, NodeClassDType)) { fromRange = adtypep->declRange(); } @@ -246,6 +248,15 @@ private: if (debug()>=9) newp->dumpTree(cout, "--SELBTn: "); nodep->replaceWith(newp); pushDeletep(nodep); VL_DANGLING(nodep); } + else if (AstAssocArrayDType* adtypep = VN_CAST(ddtypep, AssocArrayDType)) { + // SELBIT(array, index) -> ASSOCSEL(array, index) + AstNode* subp = rhsp; + AstAssocSel* newp = new AstAssocSel(nodep->fileline(), + fromp, subp); + newp->dtypeFrom(adtypep->subDTypep()); // Need to strip off array reference + if (debug()>=9) newp->dumpTree(cout, "--SELBTn: "); + nodep->replaceWith(newp); pushDeletep(nodep); VL_DANGLING(nodep); + } else if (VN_IS(ddtypep, BasicDType)) { // SELBIT(range, index) -> SEL(array, index, 1) AstSel* newp = new AstSel(nodep->fileline(), diff --git a/src/verilog.y b/src/verilog.y index e81a80950..96570107a 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1627,9 +1627,9 @@ variable_dimension: // ==IEEE: variable_dimension | anyrange { $$ = $1; } | '[' constExpr ']' { $$ = new AstRange($1, new AstConst($1, 0), new AstSub($1, $2, new AstConst($1, 1))); } // // IEEE: associative_dimension - //UNSUP '[' data_type ']' { UNSUP } - //UNSUP yP_BRASTAR ']' { UNSUP } - //UNSUP '[' '*' ']' { UNSUP } + | '[' data_type ']' { $$ = new AstAssocRange($1, $2); } + | yP_BRASTAR ']' { $$ = NULL; v3error("Unsupported: [*] wildcard associative arrays"); } + | '[' '*' ']' { $$ = NULL; v3error("Unsupported: [*] wildcard associative arrays"); } // // IEEE: queue_dimension // // '[' '$' ']' -- $ is part of expr // // '[' '$' ':' expr ']' -- anyrange:expr:$ diff --git a/test_regress/t/t_assoc.pl b/test_regress/t/t_assoc.pl new file mode 100755 index 000000000..6b3b15be5 --- /dev/null +++ b/test_regress/t/t_assoc.pl @@ -0,0 +1,20 @@ +#!/usr/bin/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. + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc.v b/test_regress/t/t_assoc.v new file mode 100644 index 000000000..cf3293d0c --- /dev/null +++ b/test_regress/t/t_assoc.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Wilson Snyder. + +`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); +`define checkg(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%g' exp='%g'\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc=0; + + integer i; + + always @ (posedge clk) begin + cyc <= cyc + 1; + begin + // Type + typedef bit [3:0] nibble_t; + string a [nibble_t]; + string b [nibble_t]; + nibble_t k; + string v; + + a[4'd3] = "fooed"; + a[4'd2] = "bared"; + i = a.num(); `checkh(i, 2); + i = a.size; `checkh(i, 2); // Also checks no parens + v = a[4'd3]; `checks(v, "fooed"); + v = a[4'd2]; `checks(v, "bared"); + i = a.exists(4'd0); `checkh(i, 0); + i = a.exists(4'd2); `checkh(i, 1); + i = a.first(k); `checkh(i, 1); `checks(k, 4'd2); + i = a.next(k); `checkh(i, 1); `checks(k, 4'd3); + i = a.next(k); `checkh(i, 0); + i = a.last(k); `checkh(i, 1); `checks(k, 4'd3); + i = a.prev(k); `checkh(i, 1); `checks(k, 4'd2); + i = a.prev(k); `checkh(i, 0); + v = $sformatf("%p", a); `checks(v, "'{'h2:\"bared\", 'h3:\"fooed\"} "); + + a.delete(4'd2); + i = a.size(); `checkh(i, 1); + + b = a; // Copy assignment + i = b.size(); `checkh(i, 1); + end + + begin + // Strings + string a [string]; + string k; + string v; + + a["foo"] = "fooed"; + a["bar"] = "bared"; + i = a.num(); `checkh(i, 2); + i = a.size(); `checkh(i, 2); + v = a["foo"]; `checks(v, "fooed"); + v = a["bar"]; `checks(v, "bared"); + i = a.exists("baz"); `checkh(i, 0); + i = a.exists("bar"); `checkh(i, 1); + i = a.first(k); `checkh(i, 1); `checks(k, "bar"); + i = a.next(k); `checkh(i, 1); `checks(k, "foo"); + i = a.next(k); `checkh(i, 0); + i = a.last(k); `checkh(i, 1); `checks(k, "foo"); + i = a.prev(k); `checkh(i, 1); `checks(k, "bar"); + i = a.prev(k); `checkh(i, 0); + v = $sformatf("%p", a["foo"]); `checks(v, "\"fooed\""); + v = $sformatf("%p", a); `checks(v, "'{\"bar\":\"bared\", \"foo\":\"fooed\"} "); + + a.delete("bar"); + i = a.size(); `checkh(i, 1); + a.delete(); + i = a.size(); `checkh(i, 0); + i = a.first(k); `checkh(i, 0); + i = a.last(k); `checkh(i, 0); + + // Patterns & default +`ifndef VERILATOR // Unsupported: Pattern assignment + a = '{ "f": "fooed", "b": "bared", default: "defaulted" }; + i = a.size(); `checkh(i, 2); // Default doesn't count + v = a["f"]; `checks(v, "fooed"); + v = a["b"]; `checks(v, "bared"); + v = a["NEXISTS"]; `checks(v, "defaulted"); +`endif + end + + begin + // Wide-wides - need special array container classes, ick. + logic [91:2] a [ logic [65:1] ]; + a[~65'hfe] = ~ 90'hfee; + `checkh(a[~65'hfe], ~ 90'hfee); + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_assoc_meth_bad.out b/test_regress/t/t_assoc_meth_bad.out new file mode 100644 index 000000000..26cbe65dd --- /dev/null +++ b/test_regress/t/t_assoc_meth_bad.out @@ -0,0 +1,37 @@ +%Error: t/t_assoc_meth_bad.v:13: The 1 arguments passed to .num method does not match its requiring 0 arguments + : ... In instance t + v = a.num("badarg"); + ^~~ +%Error: t/t_assoc_meth_bad.v:14: The 1 arguments passed to .size method does not match its requiring 0 arguments + : ... In instance t + v = a.size("badarg"); + ^~~~ +%Error: t/t_assoc_meth_bad.v:15: The 0 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + v = a.exists(); + ^~~~~~ +%Error: t/t_assoc_meth_bad.v:16: The 2 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + v = a.exists(k, "bad2"); + ^~~~~~ +%Error: t/t_assoc_meth_bad.v:17: The 0 arguments passed to .first method does not match its requiring 1 arguments + : ... In instance t + v = a.first(); + ^~~~~ +%Error: t/t_assoc_meth_bad.v:18: The 2 arguments passed to .next method does not match its requiring 1 arguments + : ... In instance t + v = a.next(k, "bad2"); + ^~~~ +%Error: t/t_assoc_meth_bad.v:19: The 0 arguments passed to .last method does not match its requiring 1 arguments + : ... In instance t + v = a.last(); + ^~~~ +%Error: t/t_assoc_meth_bad.v:20: The 2 arguments passed to .prev method does not match its requiring 1 arguments + : ... In instance t + v = a.prev(k, "bad2"); + ^~~~ +%Error: t/t_assoc_meth_bad.v:21: The 2 arguments passed to .delete method does not match its requiring 0 to 1 arguments + : ... In instance t + a.delete(k, "bad2"); + ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_meth_bad.pl b/test_regress/t/t_assoc_meth_bad.pl new file mode 100755 index 000000000..49151b6cd --- /dev/null +++ b/test_regress/t/t_assoc_meth_bad.pl @@ -0,0 +1,18 @@ +#!/usr/bin/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. + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_meth_bad.v b/test_regress/t/t_assoc_meth_bad.v new file mode 100644 index 000000000..e935fb7d3 --- /dev/null +++ b/test_regress/t/t_assoc_meth_bad.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Wilson Snyder. + +module t (/*AUTOARG*/); + + initial begin + string a [string]; + string k; + string v; + + v = a.num("badarg"); + v = a.size("badarg"); + v = a.exists(); // Bad + v = a.exists(k, "bad2"); + v = a.first(); // Bad + v = a.next(k, "bad2"); // Bad + v = a.last(); // Bad + v = a.prev(k, "bad2"); // Bad + a.delete(k, "bad2"); + end +endmodule diff --git a/test_regress/t/t_assoc_pattern_unsup.out b/test_regress/t/t_assoc_pattern_unsup.out new file mode 100644 index 000000000..33981b55b --- /dev/null +++ b/test_regress/t/t_assoc_pattern_unsup.out @@ -0,0 +1,5 @@ +%Error: t/t_assoc_pattern_unsup.v:18: Unsupported: Assignment pattern applies against non struct/union: ASSOCARRAYDTYPE + : ... In instance t + a = '{ "f": "fooed", "b": "bared", default: "defaulted" }; + ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_pattern_unsup.pl b/test_regress/t/t_assoc_pattern_unsup.pl new file mode 100755 index 000000000..49151b6cd --- /dev/null +++ b/test_regress/t/t_assoc_pattern_unsup.pl @@ -0,0 +1,18 @@ +#!/usr/bin/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. + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_pattern_unsup.v b/test_regress/t/t_assoc_pattern_unsup.v new file mode 100644 index 000000000..5010d550a --- /dev/null +++ b/test_regress/t/t_assoc_pattern_unsup.v @@ -0,0 +1,27 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Wilson Snyder. + +`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); +`define checkg(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%g' exp='%g'\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); + +module t (/*AUTOARG*/); + + initial begin + int i; + string a [string]; + string k; + string v; + + a = '{ "f": "fooed", "b": "bared", default: "defaulted" }; + i = a.size(); `checkh(i, 2); // Default doesn't count + v = a["f"]; `checks(v, "fooed"); + v = a["b"]; `checks(v, "bared"); + v = a["NEXISTS"]; `checks(v, "defaulted"); + + $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 new file mode 100644 index 000000000..92afb1b02 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_unsup.out @@ -0,0 +1,2 @@ +%Error: Unsupported: [*] wildcard associative arrays +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_wildcard_unsup.pl b/test_regress/t/t_assoc_wildcard_unsup.pl new file mode 100755 index 000000000..49151b6cd --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_unsup.pl @@ -0,0 +1,18 @@ +#!/usr/bin/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. + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_wildcard_unsup.v b/test_regress/t/t_assoc_wildcard_unsup.v new file mode 100644 index 000000000..e4ddb0697 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_unsup.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Wilson Snyder. + +`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); +`define checkg(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%g' exp='%g'\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc=0; + + integer i; + + always @ (posedge clk) begin + cyc <= cyc + 1; + begin + // Wildcard + string a [*]; + int k; + string v; + + 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"); + 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); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_savable.v b/test_regress/t/t_savable.v index bef0ad54e..ac168cf85 100644 --- a/test_regress/t/t_savable.v +++ b/test_regress/t/t_savable.v @@ -34,6 +34,7 @@ module sub (/*AUTOARG*/ real r; string s,s2; string sarr[2:1]; + string assoc[string]; string si; @@ -63,6 +64,7 @@ module sub (/*AUTOARG*/ s <= "hello"; sarr[1] <= "sarr[1]"; sarr[2] <= "sarr[2]"; + assoc["mapped"] <= "Is mapped"; end if (cyc==1) begin if ($test$plusargs("save_restore")!=0) begin @@ -88,8 +90,9 @@ module sub (/*AUTOARG*/ $display("%s",s); $display("%s",sarr[1]); $display("%s",sarr[2]); - $write("*-* All Finished *-*\n"); - $finish; + if (assoc["mapped"] != "Is mapped") $stop; + $write("*-* All Finished *-*\n"); + $finish; end end endmodule diff --git a/test_regress/t/t_trace_complex.v b/test_regress/t/t_trace_complex.v index eb7588711..22f744289 100644 --- a/test_regress/t/t_trace_complex.v +++ b/test_regress/t/t_trace_complex.v @@ -49,6 +49,9 @@ module t (clk); real v_arr_real [2]; string v_string; + string v_assoc[string]; + initial v_assoc["key"] = "value"; + typedef struct packed { logic [31:0] data; } str32_t;