diff --git a/Changes b/Changes index f8ca13d87..7ae60b8a9 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,8 @@ indicates the contributor was also the author of the fix; Thanks! ** SystemPerl mode is deprecated and now untested. +*** Add 'string' printing and comparisons, bug746, bug747, etc. + *** Inline C functions that are used only once, msg1525. [Jie Xu] diff --git a/bin/verilator b/bin/verilator index bc2d99623..6fcea3a1f 100755 --- a/bin/verilator +++ b/bin/verilator @@ -2597,8 +2597,9 @@ All specify blocks and timing checks are ignored. =item string -String is supported only to the point that they can be passed to DPI -imports. +String is supported only to the point that they can be assigned, +concatenated, compared, and passed to DPI imports. Standard method calls +on strings are not supported. =item timeunit, timeprecision diff --git a/include/verilated.cpp b/include/verilated.cpp index 9c6c26326..af0c0a410 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -343,6 +343,12 @@ void _vl_vsformat(string& output, const char* formatp, va_list ap) { output += cstrp; break; } + case '@': { // Verilog/C++ string + va_arg(ap, int); // # bits is ignored + const string* cstrp = va_arg(ap, const string*); + output += *cstrp; + break; + } case 'e': case 'f': case 'g': { diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index 940c66983..3da1b2060 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -41,13 +41,24 @@ inline string VL_CVT_PACK_STR_NQ(QData lhs) { IData lw[2]; VL_SET_WQ(lw, lhs); return VL_CVT_PACK_STR_NW(2, lw); } -inline string VL_CVT_PACK_STR_NQ(string lhs) { +inline string VL_CVT_PACK_STR_NQ(const string& lhs) { return lhs; } inline string VL_CVT_PACK_STR_NI(IData lhs) { IData lw[1]; lw[0] = lhs; return VL_CVT_PACK_STR_NW(1, lw); } +inline string VL_CONCATN_NNN(const string& lhs, const string& rhs) { + return lhs+rhs; +} +inline string VL_REPLICATEN_NNQ(int,int,int rbits, const string& lhs, IData rep) { + string out; out.reserve(lhs.length() * rep); + for (unsigned times=0; timesVL_QUADSIZE); } bool isDouble() const; bool isSigned() const; + bool isString() const; AstNUser* user1p() const { // Slows things down measurably, so disabled by default @@ -1119,6 +1120,7 @@ public: void dtypeSetLogicSized(int width, int widthMin, AstNumeric numeric) { dtypep(findLogicDType(width,widthMin,numeric)); } void dtypeSetLogicBool() { dtypep(findLogicBoolDType()); } void dtypeSetDouble() { dtypep(findDoubleDType()); } + void dtypeSetString() { dtypep(findStringDType()); } void dtypeSetSigned32() { dtypep(findSigned32DType()); } void dtypeSetUInt32() { dtypep(findUInt32DType()); } // Twostate void dtypeSetUInt64() { dtypep(findUInt64DType()); } // Twostate @@ -1126,6 +1128,7 @@ public: // Data type locators AstNodeDType* findLogicBoolDType() { return findBasicDType(AstBasicDTypeKwd::LOGIC); } AstNodeDType* findDoubleDType() { return findBasicDType(AstBasicDTypeKwd::DOUBLE); } + AstNodeDType* findStringDType() { return findBasicDType(AstBasicDTypeKwd::STRING); } AstNodeDType* findSigned32DType() { return findBasicDType(AstBasicDTypeKwd::INTEGER); } AstNodeDType* findUInt32DType() { return findBasicDType(AstBasicDTypeKwd::UINT32); } // Twostate AstNodeDType* findUInt64DType() { return findBasicDType(AstBasicDTypeKwd::UINT64); } // Twostate @@ -1263,8 +1266,9 @@ public: virtual void numberOperate(V3Number& out, const V3Number& lhs) = 0; // Set out to evaluation of a AstConst'ed lhs virtual bool cleanLhs() = 0; virtual bool sizeMattersLhs() = 0; // True if output result depends on lhs size - virtual bool signedFlavor() const { return false; } // Signed flavor of nodes with both flavors? virtual bool doubleFlavor() const { return false; } // D flavor of nodes with both flavors? + virtual bool signedFlavor() const { return false; } // Signed flavor of nodes with both flavors? + virtual bool stringFlavor() const { return false; } // N flavor of nodes with both flavors? virtual int instrCount() const { return widthInstrs(); } virtual V3Hash sameHash() const { return V3Hash(); } virtual bool same(AstNode*) const { return true; } @@ -1287,8 +1291,9 @@ public: virtual bool cleanRhs() = 0; // True if RHS must have extra upper bits zero virtual bool sizeMattersLhs() = 0; // True if output result depends on lhs size virtual bool sizeMattersRhs() = 0; // True if output result depends on rhs size - virtual bool signedFlavor() const { return false; } // Signed flavor of nodes with both flavors? virtual bool doubleFlavor() const { return false; } // D flavor of nodes with both flavors? + virtual bool signedFlavor() const { return false; } // Signed flavor of nodes with both flavors? + virtual bool stringFlavor() const { return false; } // N flavor of nodes with both flavors? virtual int instrCount() const { return widthInstrs(); } virtual V3Hash sameHash() const { return V3Hash(); } virtual bool same(AstNode*) const { return true; } @@ -1886,6 +1891,7 @@ inline int AstNode::widthMin() const { return dtypep() ? dtypep()->widthMin() : inline bool AstNode::width1() const { return dtypep() && dtypep()->width()==1; } // V3Const uses to know it can optimize inline int AstNode::widthInstrs() const { return (!dtypep() ? 1 : (dtypep()->isWide() ? dtypep()->widthWords() : 1)); } inline bool AstNode::isDouble() const { return dtypep() && dtypep()->castBasicDType() && dtypep()->castBasicDType()->isDouble(); } +inline bool AstNode::isString() const { return dtypep() && dtypep()->basicp() && dtypep()->basicp()->isString(); } inline bool AstNode::isSigned() const { return dtypep() && dtypep()->isSigned(); } inline bool AstNode::isZero() { return (this->castConst() && this->castConst()->num().isEqZero()); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index aa044b795..fd3aa7087 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -737,7 +737,10 @@ void AstNode::dump(ostream& str) { } else { // V3Broken will throw an error if (dtypep()) str<<" %Error-dtype-exp=null,got="<<(void*)dtypep(); } - if (name()!="") str<<" "<castConstString()->name(); } - virtual int instrCount() const { return 2; } // C just loads a pointer - virtual string name() const { return m_name; } - void name(const string& flag) { m_name = flag; rewidth(); } -}; - class AstRange : public AstNode { // Range specification, for use under variables and cells private: @@ -400,6 +378,7 @@ public: bool isBitLogic() const { return keyword().isBitLogic(); } bool isDouble() const { return keyword().isDouble(); } bool isOpaque() const { return keyword().isOpaque(); } + bool isString() const { return keyword().isString(); } bool isSloppy() const { return keyword().isSloppy(); } bool isZeroInit() const { return keyword().isZeroInit(); } bool isRanged() const { return rangep() || m.m_nrange.ranged(); } @@ -935,6 +914,7 @@ private: bool m_isPulldown:1; // Tri0 bool m_isPullup:1; // Tri1 bool m_isIfaceParent:1; // dtype is reference to interface present in this module + bool m_noSubst:1; // Do not substitute out references bool m_trace:1; // Trace this variable void init() { @@ -947,6 +927,7 @@ private: m_attrClockEn=false; m_attrScBv=false; m_attrIsolateAssign=false; m_attrSFormat=false; m_fileDescr=false; m_isConst=false; m_isStatic=false; m_isPulldown=false; m_isPullup=false; m_isIfaceParent=false; + m_noSubst=false; m_trace=false; } public: @@ -1039,7 +1020,9 @@ public: void isIfaceParent(bool flag) { m_isIfaceParent = flag; } void funcLocal(bool flag) { m_funcLocal = flag; } void funcReturn(bool flag) { m_funcReturn = flag; } - void trace(bool flag) { m_trace=flag; } + void noSubst(bool flag) { m_noSubst=flag; } + bool noSubst() const { return m_noSubst; } + void trace(bool flag) { m_trace=flag; } // METHODS virtual void name(const string& name) { m_name = name; } virtual string directionName() const { return (isInout() ? "inout" : isInput() ? "input" @@ -3521,10 +3504,10 @@ public: }; class AstCvtPackString : public AstNodeUniop { - // Convert to Verilator Packed Pack (aka Pack) + // Convert to Verilator Packed String (aka verilog "string") public: AstCvtPackString(FileLine* fl, AstNode* lhsp) : AstNodeUniop(fl, lhsp) { - dtypeSetUInt64(); } // Really, width should be dtypep -> STRING + dtypeSetString(); } // Really, width should be dtypep -> STRING ASTNODE_NODE_FUNCS(CvtPackString, CVTPACKSTRING) virtual void numberOperate(V3Number& out, const V3Number& lhs) { V3ERROR_NA; } virtual string emitVerilog() { return "%f$_CAST(%l)"; } @@ -3794,6 +3777,21 @@ public: virtual int instrCount() const { return instrCountDouble(); } virtual bool doubleFlavor() const { return true; } }; +class AstEqN : public AstNodeBiCom { +public: + AstEqN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiCom(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(EqN, EQN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opEqN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f== %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return "=="; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstNeq : public AstNodeBiCom { public: AstNeq(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiCom(fl, lhsp, rhsp) { @@ -3822,6 +3820,21 @@ public: virtual int instrCount() const { return instrCountDouble(); } virtual bool doubleFlavor() const { return true; } }; +class AstNeqN : public AstNodeBiCom { +public: + AstNeqN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiCom(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(NeqN, NEQN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opNeqN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f!= %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return "!="; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstLt : public AstNodeBiop { public: AstLt(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { @@ -3864,6 +3877,21 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual bool signedFlavor() const { return true; } }; +class AstLtN : public AstNodeBiop { +public: + AstLtN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(LtN, LTN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLtN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f< %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return "<"; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstGt : public AstNodeBiop { public: AstGt(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { @@ -3906,6 +3934,21 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual bool signedFlavor() const { return true; } }; +class AstGtN : public AstNodeBiop { +public: + AstGtN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(GtN, GTN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGtN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f> %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return ">"; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstGte : public AstNodeBiop { public: AstGte(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { @@ -3949,6 +3992,21 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual bool signedFlavor() const { return true; } }; +class AstGteN : public AstNodeBiop { +public: + AstGteN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(GteN, GTEN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGteN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f>= %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return ">="; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstLte : public AstNodeBiop { public: AstLte(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { @@ -3992,6 +4050,21 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual bool signedFlavor() const { return true; } }; +class AstLteN : public AstNodeBiop { +public: + AstLteN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetLogicBool(); } + ASTNODE_NODE_FUNCS(LteN, LTEN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLteN(lhs,rhs); } + virtual string emitVerilog() { return "%k(%l %f<= %r)"; } + virtual string emitC() { V3ERROR_NA; return ""; } + virtual string emitSimpleOperator() { return "<="; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstShiftL : public AstNodeBiop { public: AstShiftL(FileLine* fl, AstNode* lhsp, AstNode* rhsp, int setwidth=0) @@ -4352,6 +4425,22 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual int instrCount() const { return widthInstrs()*2; } }; +class AstConcatN : public AstNodeBiop { + // String concatenate +public: + AstConcatN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetString(); + } + ASTNODE_NODE_FUNCS(ConcatN, CONCATN) + virtual string emitVerilog() { return "%f{%l, %k%r}"; } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opConcatN(lhs,rhs); } + virtual string emitC() { return "VL_CONCATN_NNN(%li, %ri)"; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return true;} virtual bool cleanRhs() {return true;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return instrCountString(); } + virtual bool stringFlavor() const { return true; } +}; class AstReplicate : public AstNodeBiop { // Also used as a "Uniop" flavor of Concat, e.g. "{a}" // Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp() @@ -4377,6 +4466,25 @@ public: virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual int instrCount() const { return widthInstrs()*2; } }; +class AstReplicateN : public AstNodeBiop { + // String replicate +private: + void init() { dtypeSetString(); } +public: + AstReplicateN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) + : AstNodeBiop(fl, lhsp, rhsp) { init(); } + AstReplicateN(FileLine* fl, AstNode* lhsp, uint32_t repCount) + : AstNodeBiop(fl, lhsp, new AstConst(fl, repCount)) { init(); } + ASTNODE_NODE_FUNCS(ReplicateN, REPLICATEN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opReplN(lhs,rhs); } + virtual string emitVerilog() { return "%f{%r{%k%l}}"; } + virtual string emitC() { return "VL_REPLICATEN_NN%rq(0,0,%rw, %li, %ri)"; } + virtual bool cleanOut() {return false;} + virtual bool cleanLhs() {return true;} virtual bool cleanRhs() {return true;} + virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return widthInstrs()*2; } + virtual bool stringFlavor() const { return true; } +}; class AstStreamL : public AstNodeStream { // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() public: diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 259c5a5de..08cbe161b 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -624,7 +624,7 @@ private: void replaceConstString (AstNode* oldp, const string& num) { // Replace oldp node with a constant set to specified value UASSERT (oldp, "Null old\n"); - AstNode* newp = new AstConstString(oldp->fileline(), num); + AstNode* newp = new AstConst(oldp->fileline(), AstConst::String(), num); if (debug()>5) oldp->dumpTree(cout," const_old: "); if (debug()>5) newp->dumpTree(cout," _new: "); oldp->replaceWith(newp); @@ -1457,6 +1457,7 @@ private: && v3Global.opt.oConst() && !(nodep->varp()->isFuncLocal() // Default value, not a "known" constant for this usage && nodep->varp()->isInput()) + && !nodep->varp()->noSubst() && !nodep->varp()->isSigPublic()) || nodep->varp()->isParam())) { if (operandConst(valuep)) { @@ -2141,22 +2142,28 @@ private: TREEOP ("AstXor {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstEq {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. TREEOP ("AstEqD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. + TREEOP ("AstEqN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. TREEOP ("AstEqCase {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstEqWild {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGt {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGtD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); + TREEOP ("AstGtN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGtS {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGte {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGteD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); + TREEOP ("AstGteN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGteS {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLt {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLtD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); + TREEOP ("AstLtN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLtS {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLte {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLteD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); + TREEOP ("AstLteN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLteS {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstNeq {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); + TREEOP ("AstNeqN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqCase{operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqWild{operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLogAnd {operandsSame($lhsp,,$rhsp), $lhsp.width1}", "replaceWLhs(nodep)"); diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index fe26a87b6..eb1072fca 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -620,6 +620,10 @@ public: // Put out constant set to the specified variable, or given variable in a string if (nodep->num().isFourState()) { nodep->v3error("Unsupported: 4-state numbers in this context"); + } else if (nodep->num().isString()) { + putbs("string("); + putsQuoted(nodep->num().toString()); + puts(")"); } else if (nodep->isWide()) { putbs("VL_CONST_W_"); puts(cvtToStr(VL_WORDS_I(nodep->num().widthMin()))); @@ -680,9 +684,6 @@ public: emitConstant(nodep, NULL, ""); } } - virtual void visit(AstConstString* nodep, AstNUser*) { - putsQuoted(nodep->name()); - } // Just iterate virtual void visit(AstNetlist* nodep, AstNUser*) { @@ -1168,17 +1169,20 @@ void EmitCStmts::emitOpName(AstNode* nodep, const string& format, struct EmitDispState { string m_format; // "%s" and text from user + vector m_argsChar; // Format of each argument to be printed vector m_argsp; // Each argument to be printed vector m_argsFunc; // Function before each argument to be printed EmitDispState() { clear(); } void clear() { m_format = ""; + m_argsChar.clear(); m_argsp.clear(); m_argsFunc.clear(); } void pushFormat(const string& fmt) { m_format += fmt; } void pushFormat(char fmt) { m_format += fmt; } - void pushArg(AstNode* nodep, const string& func) { + void pushArg(char fmtChar, AstNode* nodep, const string& func) { + m_argsChar.push_back(fmtChar); m_argsp.push_back(nodep); m_argsFunc.push_back(func); } } emitDispState; @@ -1231,6 +1235,7 @@ void EmitCStmts::displayEmit(AstNode* nodep, bool isScan) { // Arguments for (unsigned i=0; i < emitDispState.m_argsp.size(); i++) { puts(","); + char fmt = emitDispState.m_argsChar[i]; AstNode* argp = emitDispState.m_argsp[i]; string func = emitDispState.m_argsFunc[i]; ofp()->indentInc(); @@ -1238,8 +1243,10 @@ void EmitCStmts::displayEmit(AstNode* nodep, bool isScan) { if (func!="") puts(func); if (argp) { if (isScan) puts("&("); + else if (fmt == '@') puts("&("); argp->iterate(*this); if (isScan) puts(")"); + else if (fmt == '@') puts(")"); } ofp()->indentDec(); } @@ -1288,8 +1295,8 @@ void EmitCStmts::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, pfmt = string("%") + vfmt + fmtLetter; } emitDispState.pushFormat(pfmt); - emitDispState.pushArg(NULL,cvtToStr(argp->widthMin())); - emitDispState.pushArg(argp,""); + emitDispState.pushArg(' ',NULL,cvtToStr(argp->widthMin())); + emitDispState.pushArg(fmtLetter,argp,""); // Next parameter *elistp = (*elistp)->nextp(); @@ -1328,6 +1335,7 @@ void EmitCStmts::displayNode(AstNode* nodep, AstScopeName* scopenamep, break; // Special codes case '~': displayArg(nodep,&elistp,isScan, vfmt,'d'); break; // Signed decimal + case '@': displayArg(nodep,&elistp,isScan, vfmt,'@'); break; // Packed string // Spec: h d o b c l case 'b': displayArg(nodep,&elistp,isScan, vfmt,'b'); break; case 'c': displayArg(nodep,&elistp,isScan, vfmt,'c'); break; @@ -1345,7 +1353,7 @@ void EmitCStmts::displayNode(AstNode* nodep, AstScopeName* scopenamep, string suffix = scopenamep->scopePrettyName(); if (suffix=="") emitDispState.pushFormat("%S"); else emitDispState.pushFormat("%N"); // Add a . when needed - emitDispState.pushArg(NULL, "vlSymsp->name()"); + emitDispState.pushArg(' ',NULL, "vlSymsp->name()"); emitDispState.pushFormat(suffix); break; } diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index b33c0e6cc..35d8cc69c 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -553,10 +553,6 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { virtual void visit(AstConst* nodep, AstNUser*) { putfs(nodep,nodep->num().ascii(true,true)); } - virtual void visit(AstConstString* nodep, AstNUser*) { - putfs(nodep,""); - putsQuoted(nodep->name()); - } // Just iterate virtual void visit(AstTopScope* nodep, AstNUser*) { diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 6ea325bd6..73606a724 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -365,6 +365,8 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const { if (width()!=64) out<<"%E-bad-width-double"; else out<width()-1; @@ -606,6 +612,7 @@ vlsint64_t V3Number::toSQuad() const { string V3Number::toString() const { UASSERT(!isFourState(),"toString with 4-state "<<*this); // Spec says always drop leading zeros, this isn't quite right, we space pad. + if (isString()) return m_stringVal; int bit=this->width()-1; bool start=true; while ((bit%8)!=7) bit++; @@ -1651,3 +1658,38 @@ V3Number& V3Number::opLtD (const V3Number& lhs, const V3Number& rhs) { V3Number& V3Number::opLteD (const V3Number& lhs, const V3Number& rhs) { return setSingleBits(lhs.toDouble() <= rhs.toDouble()); } + +//====================================================================== +// Ops - String + +V3Number& V3Number::opConcatN (const V3Number& lhs, const V3Number& rhs) { + return setString(lhs.toString() + rhs.toString()); +} +V3Number& V3Number::opReplN (const V3Number& lhs, const V3Number& rhs) { + return opReplN(lhs, rhs.toUInt()); +} +V3Number& V3Number::opReplN (const V3Number& lhs, uint32_t rhsval) { + string out; out.reserve(lhs.toString().length() * rhsval); + for (unsigned times=0; times rhs.toString()); +} +V3Number& V3Number::opGteN (const V3Number& lhs, const V3Number& rhs) { + return setSingleBits(lhs.toString() >= rhs.toString()); +} +V3Number& V3Number::opLtN (const V3Number& lhs, const V3Number& rhs) { + return setSingleBits(lhs.toString() < rhs.toString()); +} +V3Number& V3Number::opLteN (const V3Number& lhs, const V3Number& rhs) { + return setSingleBits(lhs.toString() <= rhs.toString()); +} diff --git a/src/V3Number.h b/src/V3Number.h index 20a30b893..e002eaabd 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -35,13 +35,16 @@ class V3Number { bool m_sized:1; // True if the user specified the width, else we track it. bool m_signed:1; // True if signed value bool m_double:1; // True if double real value + bool m_isString:1; // True if string bool m_fromString:1; // True if from string literal bool m_autoExtend:1; // True if SystemVerilog extend-to-any-width FileLine* m_fileline; vector m_value; // The Value, with bit 0 being in bit 0 of this vector (unless X/Z) vector m_valueX; // Each bit is true if it's X or Z, 10=z, 11=x + string m_stringVal; // If isString, the value of the string // METHODS V3Number& setSingleBits(char value); + V3Number& setString(const string& str) { m_isString=true; m_stringVal=str; return *this; } void opCleanThis(); public: FileLine* fileline() const { return m_fileline; } @@ -116,19 +119,22 @@ private: V3Number& opModDivGuts(const V3Number& lhs, const V3Number& rhs, bool is_modulus); public: - class VerilogStringLiteral {}; // for creator type-overload selection // CONSTRUCTORS V3Number(FileLine* fileline) { init(fileline, 1); } V3Number(FileLine* fileline, int width) { init(fileline, width); } // 0=unsized V3Number(FileLine* fileline, int width, uint32_t value) { init(fileline, width); m_value[0]=value; opCleanThis(); } V3Number(FileLine* fileline, const char* source); // Create from a verilog 32'hxxxx number. + class VerilogStringLiteral {}; // for creator type-overload selection V3Number(VerilogStringLiteral, FileLine* fileline, const string& vvalue); + class String {}; + V3Number(String, FileLine* fileline, const string& value) { init(fileline, 0); setString(value); } private: void init(FileLine* fileline, int swidth) { m_fileline = fileline; m_signed = false; m_double = false; + m_isString = false; m_autoExtend = false; m_fromString = false; width(swidth); @@ -166,6 +172,8 @@ public: bool isSigned() const { return m_signed; } // Only correct for parsing of numbers from strings, otherwise not used (use AstConst::isSigned()) bool isDouble() const { return m_double; } // Only correct for parsing of numbers from strings, otherwise not used (use AstConst::isSigned()) void isDouble(bool flag) { m_double=flag; } // Only if have 64 bit value loaded, and want to indicate it's real + bool isString() const { return m_isString; } + void isString(bool flag) { m_isString=flag; } bool isNegative() const { return bitIs1(width()-1); } bool isFourState() const { for (int i=0;ifirstAbovep()->castArraySel()) { // ArraySel's are pointer refs, ignore } else { UINFO(4,"Cre Temp: "<8) nodep->dumpTree(cout,"deepin:"); AstNRelinker linker; nodep->unlinkFrBack(&linker); AstVar* varp = getBlockTemp(nodep); + if (noSubst) varp->noSubst(true); // Do not remove varrefs to this in V3Const // Replace node tree with reference to var AstVarRef* newp = new AstVarRef (nodep->fileline(), varp, false); linker.relink(newp); @@ -236,7 +237,7 @@ private: if (noopt && !nodep->user1()) { // Need to do this even if not wide, as e.g. a select may be on a wide operator UINFO(4,"Deep temp for LHS/RHS\n"); - createDeepTemp(nodep->rhsp()); + createDeepTemp(nodep->rhsp(), false); } } nodep->rhsp()->iterateAndNext(*this); @@ -337,7 +338,7 @@ private: && !nodep->condp()->castVarRef()) { // We're going to need the expression several times in the expanded code, // so might as well make it a common expression - createDeepTemp(nodep->condp()); + createDeepTemp(nodep->condp(), false); } checkNode(nodep); } @@ -361,6 +362,17 @@ private: } } } + virtual void visit(AstSFormatF* nodep, AstNUser*) { + nodep->iterateChildren(*this); + // Any strings sent to a display must be var of string data type, + // to avoid passing a pointer to a temporary. + for (AstNode* expp=nodep->exprsp(); expp; expp = expp->nextp()) { + if (expp->dtypep()->basicp()->isString() + && !expp->castVarRef()) { + createDeepTemp(expp, true); + } + } + } //-------------------- // Default: Just iterate diff --git a/src/V3Subst.cpp b/src/V3Subst.cpp index 150e6f43e..e44e2d612 100644 --- a/src/V3Subst.cpp +++ b/src/V3Subst.cpp @@ -258,6 +258,9 @@ private: return entryp; } } + inline bool isSubstVar(AstVar* nodep) { + return nodep->isStatementTemp() && !nodep->noSubst(); + } // VISITORS virtual void visit(AstNodeAssign* nodep, AstNUser*) { @@ -266,7 +269,7 @@ private: nodep->rhsp()->iterateAndNext(*this); bool hit=false; if (AstVarRef* varrefp = nodep->lhsp()->castVarRef()) { - if (varrefp->varp()->isStatementTemp()) { + if (isSubstVar(varrefp->varp())) { SubstVarEntry* entryp = getEntryp(varrefp); hit = true; if (m_ops > SUBST_MAX_OPS_SUBST) { @@ -281,7 +284,7 @@ private: else if (AstWordSel* wordp = nodep->lhsp()->castWordSel()) { if (AstVarRef* varrefp = wordp->lhsp()->castVarRef()) { if (wordp->rhsp()->castConst() - && varrefp->varp()->isStatementTemp()) { + && isSubstVar(varrefp->varp())) { int word = wordp->rhsp()->castConst()->toUInt(); SubstVarEntry* entryp = getEntryp(varrefp); hit = true; @@ -314,7 +317,7 @@ private: nodep->rhsp()->accept(*this); AstVarRef* varrefp = nodep->lhsp()->castVarRef(); AstConst* constp = nodep->rhsp()->castConst(); - if (varrefp && varrefp->varp()->isStatementTemp() + if (varrefp && isSubstVar(varrefp->varp()) && !varrefp->lvalue() && constp) { // Nicely formed lvalues handled in NodeAssign @@ -344,7 +347,7 @@ private: nodep->varp()->user2(m_assignStep); UINFO(9, " ASSIGNstep u2="<varp()->user2()<<" "<varp()->isStatementTemp()) { + if (isSubstVar(nodep->varp())) { SubstVarEntry* entryp = getEntryp (nodep); if (nodep->lvalue()) { UINFO(8," ASSIGNcpx "<lhsp()->isString() + || nodep->rhsp()->isString()) { + AstNode* newp = new AstConcatN (nodep->fileline(),nodep->lhsp()->unlinkFrBack(), + nodep->rhsp()->unlinkFrBack()); + nodep->replaceWith(newp); + pushDeletep(nodep); nodep=NULL; + return; + } + } + if (vup->c()->final()) { + if (!nodep->dtypep()->widthSized()) { + // See also error in V3Number + nodep->v3warn(WIDTHCONCAT,"Unsized numbers/parameters not allowed in concatenations."); + } + } + } + virtual void visit(AstConcatN* nodep, AstNUser* vup) { + // String concatenate. + // Already did AstConcat simplifications + if (vup->c()->prelim()) { + iterateCheckString(nodep,"LHS",nodep->lhsp(),BOTH); + iterateCheckString(nodep,"RHS",nodep->rhsp(),BOTH); + nodep->dtypeSetString(); } if (vup->c()->final()) { if (!nodep->dtypep()->widthSized()) { @@ -390,9 +420,38 @@ private: if (times==0 && !nodep->backp()->castConcat()) { // Concat Visitor will clean it up. nodep->v3error("Replication value of 0 is only legal under a concatenation."); times=1; } - nodep->dtypeSetLogicSized((nodep->lhsp()->width() * times), - (nodep->lhsp()->widthMin() * times), - AstNumeric::UNSIGNED); + if (nodep->lhsp()->isString()) { + AstNode* newp = new AstReplicateN(nodep->fileline(),nodep->lhsp()->unlinkFrBack(), + nodep->rhsp()->unlinkFrBack()); + nodep->replaceWith(newp); + pushDeletep(nodep); nodep=NULL; + return; + } else { + nodep->dtypeSetLogicSized((nodep->lhsp()->width() * times), + (nodep->lhsp()->widthMin() * times), + AstNumeric::UNSIGNED); + } + } + if (vup->c()->final()) { + if (!nodep->dtypep()->widthSized()) { + // See also error in V3Number + nodep->v3warn(WIDTHCONCAT,"Unsized numbers/parameters not allowed in replications."); + } + } + } + virtual void visit(AstReplicateN* nodep, AstNUser* vup) { + // Replicate with string + if (vup->c()->prelim()) { + iterateCheckString(nodep,"LHS",nodep->lhsp(),BOTH); + iterateCheckSizedSelf(nodep,"RHS",nodep->rhsp(),SELF,BOTH); + V3Const::constifyParamsEdit(nodep->rhsp()); // rhsp may change + AstConst* constp = nodep->rhsp()->castConst(); + if (!constp) { nodep->v3error("Replication value isn't a constant."); return; } + uint32_t times = constp->toUInt(); + if (times==0 && !nodep->backp()->castConcat()) { // Concat Visitor will clean it up. + nodep->v3error("Replication value of 0 is only legal under a concatenation."); times=1; + } + nodep->dtypeSetString(); } if (vup->c()->final()) { if (!nodep->dtypep()->widthSized()) { @@ -658,9 +717,6 @@ private: // We don't size the constant until we commit the widths, as need parameters // to remain unsized, and numbers to remain unsized to avoid backp() warnings } - virtual void visit(AstConstString* nodep, AstNUser* vup) { - nodep->rewidth(); - } virtual void visit(AstRand* nodep, AstNUser* vup) { if (vup->c()->prelim()) { nodep->dtypeSetSigned32(); // Says the spec @@ -1701,6 +1757,13 @@ private: if (argp) argp=argp->nextp(); break; } + case 's': { // Convert string to pack string + if (argp && argp->dtypep()->basicp()->isString()) { // Convert it + ch = '@'; + } + if (argp) argp=argp->nextp(); + break; + } default: { // Most operators, just move to next argument if (argp) argp=argp->nextp(); break; @@ -2201,6 +2264,12 @@ private: iterateCheckReal(nodep,"LHS",nodep->lhsp(),FINAL); iterateCheckReal(nodep,"RHS",nodep->rhsp(),FINAL); } + } else if (nodep->lhsp()->isString() || nodep->rhsp()->isString()) { + if (AstNodeBiop* newp=replaceWithNVersion(nodep)) { nodep=NULL; + nodep = newp; // Process new node instead + iterateCheckString(nodep,"LHS",nodep->lhsp(),FINAL); + iterateCheckString(nodep,"RHS",nodep->rhsp(),FINAL); + } } else { bool signedFl = nodep->lhsp()->isSigned() && nodep->rhsp()->isSigned(); if (AstNodeBiop* newp=replaceWithUOrSVersion(nodep, signedFl)) { nodep=NULL; @@ -2230,6 +2299,19 @@ private: nodep->dtypeSetLogicBool(); } } + void visit_cmp_string(AstNodeBiop* nodep, AstNUser* vup) { + // CALLER: EqN, LtN + // Widths: 1 bit out, lhs width == rhs width + // String compare (not output) + // Real if and only if real_allow set + if (!nodep->rhsp()) nodep->v3fatalSrc("For binary ops only!"); + if (vup->c()->prelim()) { + // See similar handling in visit_cmp_eq_gt where created + iterateCheckString(nodep,"LHS",nodep->lhsp(),BOTH); + iterateCheckString(nodep,"RHS",nodep->rhsp(),BOTH); + nodep->dtypeSetLogicBool(); + } + } void visit_negate_not(AstNodeUniop* nodep, AstNUser* vup, bool real_ok) { // CALLER: (real_ok=false) Not @@ -2596,6 +2678,15 @@ private: underp = iterateCheck(nodep,side,underp,SELF,FINAL,expDTypep,EXTEND_EXP); } } + void iterateCheckString (AstNode* nodep, const char* side, AstNode* underp, Stage stage) { + if (stage & PRELIM) { + underp = underp->acceptSubtreeReturnEdits(*this,WidthVP(SELF,PRELIM).p()); + } + if (stage & FINAL) { + AstNodeDType* expDTypep = nodep->findStringDType(); + underp = iterateCheck(nodep,side,underp,SELF,FINAL,expDTypep,EXTEND_EXP); + } + } void iterateCheckSizedSelf (AstNode* nodep, const char* side, AstNode* underp, Determ determ, Stage stage) { // Coerce child to any sized-number data type; child is self-determined i.e. isolated from expected type. @@ -2679,6 +2770,9 @@ private: } else if (!expDTypep->isDouble() && underp->isDouble()) { underp = spliceCvtS(underp, true); // Round RHS underp = underp->acceptSubtreeReturnEdits(*this,WidthVP(SELF,FINAL).p()); + } else if (expDTypep->isString() && !underp->dtypep()->isString()) { + underp = spliceCvtString(underp); + underp = underp->acceptSubtreeReturnEdits(*this,WidthVP(SELF,FINAL).p()); } else { AstBasicDType* expBasicp = expDTypep->basicp(); AstBasicDType* underBasicp = underp->dtypep()->basicp(); @@ -2822,6 +2916,20 @@ private: return nodep; } } + AstNode* spliceCvtString(AstNode* nodep) { + // IEEE-2012 11.8.1: Signed: Type coercion creates signed + // 11.8.2: Argument to convert is self-determined + if (nodep && !nodep->dtypep()->basicp()->isString()) { + UINFO(6," spliceCvtString: "<unlinkFrBack(&linker); + AstNode* newp = new AstCvtPackString(nodep->fileline(), nodep); + linker.relink(newp); + return newp; + } else { + return nodep; + } + } AstNodeBiop* replaceWithUOrSVersion(AstNodeBiop* nodep, bool signedFlavorNeeded) { // Given a signed/unsigned node type, create the opposite type // Return new node or NULL if nothing @@ -2904,6 +3012,34 @@ private: pushDeletep(nodep); nodep=NULL; return newp; } + AstNodeBiop* replaceWithNVersion(AstNodeBiop* nodep) { + // Given a signed/unsigned node type, replace with string version + // Return new node or NULL if nothing + if (nodep->stringFlavor()) { + return NULL; + } + FileLine* fl = nodep->fileline(); + AstNode* lhsp = nodep->lhsp()->unlinkFrBack(); + AstNode* rhsp = nodep->rhsp()->unlinkFrBack(); + AstNodeBiop* newp = NULL; + // No width change on output;... // All below have bool or double outputs + switch (nodep->type()) { + case AstType::atEQ: case AstType::atEQCASE: newp = new AstEqN (fl,lhsp,rhsp); break; + case AstType::atNEQ: case AstType::atNEQCASE: newp = new AstNeqN (fl,lhsp,rhsp); break; + case AstType::atGT: case AstType::atGTS: newp = new AstGtN (fl,lhsp,rhsp); break; + case AstType::atGTE: case AstType::atGTES: newp = new AstGteN (fl,lhsp,rhsp); break; + case AstType::atLT: case AstType::atLTS: newp = new AstLtN (fl,lhsp,rhsp); break; + case AstType::atLTE: case AstType::atLTES: newp = new AstLteN (fl,lhsp,rhsp); break; + default: + nodep->v3fatalSrc("Node needs conversion to string, but bad case: "<replaceWith(newp); + // No width change; the default created type (bool or string) is correct + pushDeletep(nodep); nodep=NULL; + return newp; + } AstNodeUniop* replaceWithDVersion(AstNodeUniop* nodep) { // Given a signed/unsigned node type, create the opposite type // Return new node or NULL if nothing diff --git a/test_regress/t/t_savable.v b/test_regress/t/t_savable.v index 4df08d24d..a98c6878a 100644 --- a/test_regress/t/t_savable.v +++ b/test_regress/t/t_savable.v @@ -73,6 +73,7 @@ module sub (/*AUTOARG*/ if (vec[2][1] !== 32'h0201) $stop; if (vec[2][2] !== 32'h0202) $stop; if (r != 1.234) $stop; + $display("%s",s); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_string.pl b/test_regress/t/t_string.pl new file mode 100755 index 000000000..f91289753 --- /dev/null +++ b/test_regress/t/t_string.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 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. + +compile ( + ); + +execute ( + check_finished=>1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_string.v b/test_regress/t/t_string.v new file mode 100644 index 000000000..33107286f --- /dev/null +++ b/test_regress/t/t_string.v @@ -0,0 +1,91 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2014 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); + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc=0; + + reg [4*8:1] vstr; + const string s = "a"; // Check static assignment + string s2; + string s3; + reg eq; + + // Operators == != < <= > >= {a,b} {a{b}} a[b] + // a.len, a.putc, a.getc, a.toupper, a.tolower, a.compare, a.icompare, a.substr + // a.atoi, a.atohex, a.atooct, a.atobin, a.atoreal, + // a.itoa, a.hextoa, a.octoa, a.bintoa, a.realtoa + + initial begin + $sformat(vstr, "s=%s", s); + `checks(vstr, "s=a"); + `checks(s, "a"); + `checks({s,s,s}, "aaa"); + `checks({4{s}}, "aaaa"); + // Constification + `checkh(s == "a", 1'b1); + `checkh(s == "b", 1'b0); + `checkh(s != "a", 1'b0); + `checkh(s != "b", 1'b1); + `checkh(s > " ", 1'b1); + `checkh(s > "a", 1'b0); + `checkh(s >= "a", 1'b1); + `checkh(s >= "b", 1'b0); + `checkh(s < "a", 1'b0); + `checkh(s < "b", 1'b1); + `checkh(s <= " ", 1'b0); + `checkh(s <= "a", 1'b1); + end + + // Test loop + always @ (posedge clk) begin + cyc <= cyc + 1; + if (cyc==0) begin + // Setup + s2 = "c0"; + end + else if (cyc==1) begin + $sformat(vstr, "s2%s", s2); + `checks(vstr, "s2c0"); + end + else if (cyc==2) begin + s3 = s2; + $sformat(vstr, "s2%s", s3); + `checks(vstr, "s2c0"); + end + else if (cyc==3) begin + s2 = "a"; + s3 = "b"; + end + else if (cyc==4) begin + `checks({s2,s3}, "ab"); + `checks({3{s3}}, "bbb"); + `checkh(s == "a", 1'b1); + `checkh(s == "b", 1'b0); + `checkh(s != "a", 1'b0); + `checkh(s != "b", 1'b1); + `checkh(s > " ", 1'b1); + `checkh(s > "a", 1'b0); + `checkh(s >= "a", 1'b1); + `checkh(s >= "b", 1'b0); + `checkh(s < "a", 1'b0); + `checkh(s < "b", 1'b1); + `checkh(s <= " ", 1'b0); + `checkh(s <= "a", 1'b1); + end + else if (cyc==99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule