diff --git a/Changes b/Changes index a04e55c94..6deed2284 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,7 @@ The contributors that suggested a given feature are shown in []. Thanks! *** Support bounded queues. -*** Support string compare, icompare, ato* methods, bug1606. [Yutetsu TAKATSUKASA] +*** Support string compare, ato*, etc methods, bug1606. [Yutetsu TAKATSUKASA] **** Ignore `uselib to end-of-line, bug1634. [Frederic Antonin] diff --git a/include/verilated.cpp b/include/verilated.cpp index 8a53a49a9..d4f3783ff 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1826,8 +1826,33 @@ std::string VL_CVT_PACK_STR_NW(int lwords, WDataInP lwp) VL_MT_SAFE { return std::string(destout, len); } +std::string VL_PUTC_N(const std::string& lhs, IData rhs, CData ths) VL_PURE { + std::string lstring = lhs; + const vlsint32_t rhs_s = rhs; // To signed value + // 6.16.2:str.putc(i, c) does not change the value when i < 0 || i >= str.len() || c == 0 + if (0 <= rhs_s && rhs < lhs.length() && ths != 0) lstring[rhs] = ths; + return lstring; +} + +CData VL_GETC_N(const std::string& lhs, IData rhs) VL_PURE { + CData v = 0; + const vlsint32_t rhs_s = rhs; // To signed value + // 6.16.3:str.getc(i) returns 0 if i < 0 || i >= str.len() + if (0 <= rhs_s && rhs < lhs.length()) v = lhs[rhs]; + return v; +} + +std::string VL_SUBSTR_N(const std::string& lhs, IData rhs, IData ths) VL_PURE { + const vlsint32_t rhs_s = rhs; // To signed value + const vlsint32_t ths_s = ths; // To signed value + // 6.16.8:str.substr(i, j) returns an empty string when i < 0 || j < i || j >= str.len() + if (rhs_s < 0 || ths_s < rhs_s || ths >= lhs.length()) return ""; + // Second parameter of std::string::substr(i, n) is length, not position as in SystemVerilog + return lhs.substr(rhs, ths - rhs + 1); +} + IData VL_ATOI_N(const std::string& str, int base) VL_PURE { - std::string str_mod = str; // create a new instance to modify later. + std::string str_mod = str; // IEEE 1800-2017 6.16.9 says '_' may exist. str_mod.erase(std::remove(str_mod.begin(), str_mod.end(), '_'), str_mod.end()); diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index 766bafc5e..f2d395d9c 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -359,8 +359,12 @@ extern IData VL_VALUEPLUSARGS_INN(int, const std::string& ld, std::string& rdr) //====================================================================== // Strings +extern std::string VL_PUTC_N(const std::string& lhs, IData rhs, CData ths) VL_PURE; +extern CData VL_GETC_N(const std::string& lhs, IData rhs) VL_PURE; +extern std::string VL_SUBSTR_N(const std::string& lhs, IData rhs, IData ths) VL_PURE; + inline IData VL_CMP_NN(const std::string& lhs, const std::string& rhs, bool ignoreCase) VL_PURE { - // SystemVerilog Language Standard does not allow a string variable to contain '\0'. + // SystemVerilog does not allow a string variable to contain '\0'. // So C functions such as strcmp() can correctly compare strings. int result; if (ignoreCase) { diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index d93eb4722..a5887219d 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -5995,6 +5995,78 @@ public: virtual string emitC() { return "hypot(%li,%ri)"; } }; +class AstPutcN : public AstNodeTriop { + // Verilog string.putc() +public: + AstPutcN(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* ths) : AstNodeTriop(fl, lhsp, rhsp, ths) { + dtypeSetString(); + } + ASTNODE_NODE_FUNCS(PutcN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, + const V3Number& rhs, const V3Number& ths) { + out.opPutcN(lhs, rhs, ths); + } + virtual string name() const { return "putc"; } + virtual string emitVerilog() { return "%k(%l.putc(%r,%t))"; } + virtual string emitC() { return "VL_PUTC_N(%li,%ri,%ti)"; } + virtual string emitSimpleOperator() { return ""; } + virtual bool cleanOut() const { return true; } + virtual bool cleanLhs() const { return true; } + virtual bool cleanRhs() const { return true; } + virtual bool cleanThs() const { return true; } + virtual bool sizeMattersLhs() const { return false; } + virtual bool sizeMattersRhs() const { return false; } + virtual bool sizeMattersThs() const { return false; } +}; + +class AstGetcN : public AstNodeBiop { + // Verilog string.getc() +public: + AstGetcN(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + dtypeSetBitSized(8, AstNumeric::UNSIGNED); + } + ASTNODE_NODE_FUNCS(GetcN) + virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) { + return new AstGetcN(this->fileline(), lhsp, rhsp); + } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { + out.opGetcN(lhs, rhs); + } + virtual string name() const { return "getc"; } + virtual string emitVerilog() { return "%k(%l.getc(%r))"; } + virtual string emitC() { return "VL_GETC_N(%li,%ri)"; } + virtual string emitSimpleOperator() { return ""; } + virtual bool cleanOut() const { return true; } + virtual bool cleanLhs() const { return true; } + virtual bool cleanRhs() const { return true; } + virtual bool sizeMattersLhs() const { return false; } + virtual bool sizeMattersRhs() const { return false; } +}; + +class AstSubstrN : public AstNodeTriop { + // Verilog string.substr() +public: + AstSubstrN(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* ths) : AstNodeTriop(fl, lhsp, rhsp, ths) { + dtypeSetString(); + } + ASTNODE_NODE_FUNCS(SubstrN) + virtual void numberOperate(V3Number& out, const V3Number& lhs, + const V3Number& rhs, const V3Number& ths) { + out.opSubstrN(lhs, rhs, ths); + } + virtual string name() const { return "substr"; } + virtual string emitVerilog() { return "%k(%l.substr(%r,%t))"; } + virtual string emitC() { return "VL_SUBSTR_N(%li,%ri,%ti)"; } + virtual string emitSimpleOperator() { return ""; } + virtual bool cleanOut() const { return true; } + virtual bool cleanLhs() const { return true; } + virtual bool cleanRhs() const { return true; } + virtual bool cleanThs() const { return true; } + virtual bool sizeMattersLhs() const { return false; } + virtual bool sizeMattersRhs() const { return false; } + virtual bool sizeMattersThs() const { return false; } +}; + class AstCompareNN : public AstNodeBiop { // Verilog str.compare() and str.icompare() private: diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 967ffd9e9..6d7144d5d 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2526,6 +2526,8 @@ private: TREEOPV("AstLogIf{$lhsp, $rhsp}", "AstLogOr{AstLogNot{$lhsp},$rhsp}"); TREEOPV("AstLogEq{$lhsp, $rhsp}", "replaceLogEq(nodep)"); // Strings + TREEOPC("AstPutcN{$lhsp.castConst, $rhsp.castConst, $thsp.castConst}", "replaceConst(nodep)"); + TREEOPC("AstSubstrN{$lhsp.castConst, $rhsp.castConst, $thsp.castConst}", "replaceConst(nodep)"); TREEOPC("AstCvtPackString{$lhsp.castConst}", "replaceConstString(nodep, VN_CAST(nodep->lhsp(), Const)->num().toString())"); diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index ed5e8dc44..da3f45cf1 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -643,6 +643,10 @@ public: emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), NULL); } } + virtual void visit(AstNodeTriop* nodep) { + UASSERT_OBJ(!emitSimpleOk(nodep), nodep, "Triop cannot be described in a simple way"); + emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nodep->thsp()); + } virtual void visit(AstRedXor* nodep) { if (nodep->lhsp()->isWide()) { visit(VN_CAST(nodep, NodeUniop)); diff --git a/src/V3EmitCInlines.cpp b/src/V3EmitCInlines.cpp index 011e5e24d..28946e809 100644 --- a/src/V3EmitCInlines.cpp +++ b/src/V3EmitCInlines.cpp @@ -62,6 +62,18 @@ class EmitCInlines : EmitCBaseVisitor { v3Global.needHeavy(true); iterateChildren(nodep); } + virtual void visit(AstPutcN* nodep) { + v3Global.needHeavy(true); + iterateChildren(nodep); + } + virtual void visit(AstGetcN* nodep) { + v3Global.needHeavy(true); + iterateChildren(nodep); + } + virtual void visit(AstSubstrN* nodep) { + v3Global.needHeavy(true); + iterateChildren(nodep); + } virtual void visit(AstCompareNN* nodep) { v3Global.needHeavy(true); iterateChildren(nodep); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 28940635d..0696b78be 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1262,6 +1262,44 @@ V3Number& V3Number::opAtoN(const V3Number& lhs, int base) { return setLongS(static_cast(v)); } +V3Number& V3Number::opPutcN(const V3Number& lhs, const V3Number& rhs, const V3Number& ths) { + NUM_ASSERT_OP_ARGS3(lhs, rhs, ths); + NUM_ASSERT_STRING_ARGS1(lhs); + string lstring = lhs.toString(); + const vlsint32_t i = rhs.toSInt(); + const vlsint32_t c = ths.toSInt() & 0xFF; + // 6.16.2:str.putc(i, c) does not change the value when i < 0 || i >= str.len() || c == 0 + // when evaluating the second condition, i must be positive. + if (0 <= i && static_cast(i) < lstring.length() && c != 0) lstring[i] = c; + return setString(lstring); +} + +V3Number& V3Number::opGetcN(const V3Number& lhs, const V3Number& rhs) { + NUM_ASSERT_OP_ARGS2(lhs, rhs); + NUM_ASSERT_STRING_ARGS1(lhs); + const string lstring = lhs.toString(); + const vlsint32_t i = rhs.toSInt(); + vlsint32_t v = 0; + // 6.16.3:str.getc(i) returns 0 if i < 0 || i >= str.len() + // when evaluating the second condition, i must be positive. + if (0 <= i && static_cast(i) < lstring.length()) v = lstring[i]; + return setLong(v); +} + +V3Number& V3Number::opSubstrN(const V3Number& lhs, const V3Number& rhs, const V3Number& ths) { + NUM_ASSERT_OP_ARGS3(lhs, rhs, ths); + NUM_ASSERT_STRING_ARGS1(lhs); + const string lstring = lhs.toString(); + const vlsint32_t i = rhs.toSInt(); + const vlsint32_t j = ths.toSInt(); + // 6.16.8:str.substr(i, j) returns an empty string when i < 0 || j < i || j >= str.len() + // when evaluating the third condition, j must be positive because 0 <= i <= j is guaranteed by + // the former two conditions. + if (i < 0 || j < i || static_cast(j) >= lstring.length()) return setString(""); + // The second parameter of std::string::substr(i, n) is length, not position as SystemVerilog. + return setString(lstring.substr(i, j - i + 1)); +} + V3Number& V3Number::opCompareNN(const V3Number& lhs, const V3Number& rhs, bool ignoreCase) { NUM_ASSERT_OP_ARGS2(lhs, rhs); NUM_ASSERT_STRING_ARGS2(lhs, rhs); diff --git a/src/V3Number.h b/src/V3Number.h index d4ab06435..8e53198d4 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -361,6 +361,9 @@ public: // "N" - string operations V3Number& opAtoN (const V3Number& lhs, int base); + V3Number& opPutcN (const V3Number& lhs, const V3Number& rhs, const V3Number& ths); + V3Number& opGetcN (const V3Number& lhs, const V3Number& rhs); + V3Number& opSubstrN (const V3Number& lhs, const V3Number& rhs, const V3Number& ths); V3Number& opCompareNN(const V3Number& lhs,const V3Number& rhs, bool ignoreCase); V3Number& opConcatN (const V3Number& lhs, const V3Number& rhs); V3Number& opReplN (const V3Number& lhs, const V3Number& rhs); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 6eadf53a9..b7f86aabd 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -333,6 +333,38 @@ private: // Output integer, input string virtual void visit(AstLenN* nodep) { visit_Os32_string(nodep); } + virtual void visit(AstPutcN* nodep) { + // CALLER: str.putc() + UASSERT_OBJ(nodep->rhsp() && nodep->thsp(), nodep, "For ternary ops only!"); + if (m_vup && m_vup->prelim()) { + // See similar handling in visit_cmp_eq_gt where created + iterateCheckString(nodep, "LHS", nodep->lhsp(), BOTH); + iterateCheckSigned32(nodep, "RHS", nodep->rhsp(), BOTH); + iterateCheckSigned32(nodep, "THS", nodep->thsp(), BOTH); + nodep->dtypeSetString(); //AstPutcN returns the new string to be assigned by AstAssign + } + } + virtual void visit(AstGetcN* nodep) { + // CALLER: str.getc() + UASSERT_OBJ(nodep->rhsp(), nodep, "For binary ops only!"); + if (m_vup && m_vup->prelim()) { + // See similar handling in visit_cmp_eq_gt where created + iterateCheckString(nodep, "LHS", nodep->lhsp(), BOTH); + iterateCheckSigned32(nodep, "RHS", nodep->rhsp(), BOTH); + nodep->dtypeSetBitSized(8, AstNumeric::UNSIGNED); + } + } + virtual void visit(AstSubstrN* nodep) { + // CALLER: str.substr() + UASSERT_OBJ(nodep->rhsp() && nodep->thsp(), nodep, "For ternary ops only!"); + if (m_vup && m_vup->prelim()) { + // See similar handling in visit_cmp_eq_gt where created + iterateCheckString(nodep, "LHS", nodep->lhsp(), BOTH); + iterateCheckSigned32(nodep, "RHS", nodep->rhsp(), BOTH); + iterateCheckSigned32(nodep, "THS", nodep->thsp(), BOTH); + nodep->dtypeSetString(); + } + } virtual void visit(AstCompareNN* nodep) { // CALLER: str.compare(), str.icompare() // Widths: 32 bit out @@ -2115,6 +2147,34 @@ private: AstNode* rhs = argp->exprp()->unlinkFrBack(); AstNode* newp = new AstCompareNN(nodep->fileline(), lhs, rhs, ignoreCase); nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); + } else if (nodep->name() == "putc" ) { + methodOkArguments(nodep, 2, 2); + AstArg* arg0p = VN_CAST(nodep->pinsp(), Arg); + AstArg* arg1p = VN_CAST(arg0p->nextp(), Arg); + AstNodeVarRef* fromp = VN_CAST(nodep->fromp()->unlinkFrBack(), VarRef); + AstNode* rhsp = arg0p->exprp()->unlinkFrBack(); + AstNode* thsp = arg1p->exprp()->unlinkFrBack(); + AstVarRef* varrefp = new AstVarRef(nodep->fileline(), fromp->varp(), false); + AstNode* newp = new AstAssign(nodep->fileline(), fromp, + new AstPutcN(nodep->fileline(), varrefp, rhsp, thsp)); + fromp->lvalue(true); + nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); + } else if (nodep->name() == "getc") { + methodOkArguments(nodep, 1, 1); + AstArg* arg0p = VN_CAST(nodep->pinsp(), Arg); + AstNode* lhsp = nodep->fromp()->unlinkFrBack(); + AstNode* rhsp = arg0p->exprp()->unlinkFrBack(); + AstNode* newp = new AstGetcN(nodep->fileline(), lhsp, rhsp); + nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); + } else if (nodep->name() == "substr") { + methodOkArguments(nodep, 2, 2); + AstArg* arg0p = VN_CAST(nodep->pinsp(), Arg); + AstArg* arg1p = VN_CAST(arg0p->nextp(), Arg); + AstNode* lhsp = nodep->fromp()->unlinkFrBack(); + AstNode* rhsp = arg0p->exprp()->unlinkFrBack(); + AstNode* thsp = arg1p->exprp()->unlinkFrBack(); + AstNode* newp = new AstSubstrN(nodep->fileline(), lhsp, rhsp, thsp); + nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); } else if (nodep->name() == "atobin" || nodep->name() == "atohex" || nodep->name() == "atoi" @@ -2130,9 +2190,6 @@ private: methodOkArguments(nodep, 0, 0); AstNode* newp = new AstAtoN(nodep->fileline(), nodep->fromp()->unlinkFrBack(), fmt); nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); - } else if (nodep->name() == "getc" - || nodep->name() == "putc") { - nodep->v3error("Unsupported: built-in string method "<prettyNameQ()); } else { nodep->v3error("Unknown built-in string method "<prettyNameQ()); } diff --git a/test_regress/t/t_string_type_methods.v b/test_regress/t/t_string_type_methods.v index ccc47cb42..180090b7a 100644 --- a/test_regress/t/t_string_type_methods.v +++ b/test_regress/t/t_string_type_methods.v @@ -22,10 +22,13 @@ module t (/*AUTOARG*/ s="1234"; `checkh(s.len(),4); s="ab7CD"; `checks(s.toupper(), "AB7CD"); s="ab7CD"; `checks(s.tolower(), "ab7cd"); -`ifndef VERILATOR + s="1234"; s.putc(-1, "z"); `checks(s, "1234"); + s="1234"; s.putc(4, "z"); `checks(s, "1234"); + s="1234"; s.putc(2, 0); `checks(s, "1234"); s="1234"; s.putc(2, "z"); `checks(s, "12z4"); + s="1234"; `checkh(s.getc(-1), 0); + s="1234"; `checkh(s.getc(4), 0); s="1234"; `checkh(s.getc(2), "3"); -`endif s="b"; if (s.compare("a") <= 0) $stop; s="b"; if (s.compare("b") != 0) $stop; s="b"; if (s.compare("c") >= 0) $stop; @@ -38,6 +41,10 @@ module t (/*AUTOARG*/ s="b"; if (s.icompare("A") < 0) $stop; s="b"; if (s.icompare("B") != 0) $stop; s="b"; if (s.icompare("C") >= 0) $stop; + s="abcd"; `checks(s.substr(-1,1), ""); + s="abcd"; `checks(s.substr(1,0), ""); + s="abcd"; `checks(s.substr(1,4), ""); + s="abcd"; `checks(s.substr(2,3), "cd"); s="101"; `checkh(s.atoi(), 'd101); s="101"; `checkh(s.atohex(), 'h101); s="101"; `checkh(s.atooct(), 'o101); @@ -64,23 +71,35 @@ module t (/*AUTOARG*/ `checkh(s.len(),4); end else if (cyc==2) begin -`ifndef VERILATOR - s.putc(2, "z"); -`endif + s.putc(-1, "z"); end else if (cyc==3) begin -`ifndef VERILATOR - `checks(s, "12z4"); - `checkh(s.getc(2), "z"); -`endif - s="ab3CD"; + `checks(s, "1234"); + s.putc(4, "z"); end else if (cyc==4) begin + `checks(s, "1234"); + s.putc(2, 0); + end + else if (cyc==5) begin + `checks(s, "1234"); + s.putc(2, "z"); + end + else if (cyc==6) begin + `checks(s, "12z4"); + end + else if (cyc==7) begin + `checkh(s.getc(-1), 0); + `checkh(s.getc(4), 0); + `checkh(s.getc(2), "z"); + s="ab3CD"; + end + else if (cyc==8) begin `checks(s.toupper(), "AB3CD"); `checks(s.tolower(), "ab3cd"); s="b"; end - else if (cyc==5) begin + else if (cyc==9) begin if (s.compare("a") <= 0) $stop; if (s.compare("b") != 0) $stop; if (s.compare("c") >= 0) $stop; @@ -90,38 +109,45 @@ module t (/*AUTOARG*/ if (s.icompare("A") < 0) $stop; if (s.icompare("B") != 0) $stop; if (s.icompare("C") >= 0) $stop; + s="abcd"; + end + else if (cyc==10) begin + `checks(s.substr(-1,1), ""); + `checks(s.substr(1,0), ""); + `checks(s.substr(1,4), ""); + `checks(s.substr(2,3), "cd"); s="101"; end - else if (cyc==7) begin + else if (cyc==11) begin `checkh(s.atoi(), 'd101); `checkh(s.atohex(), 'h101); `checkh(s.atooct(), 'o101); `checkh(s.atobin(), 'b101); s="1.23"; end - else if (cyc==8) begin + else if (cyc==12) begin `checkg(s.atoreal(), 1.23); end - else if (cyc==9) begin + else if (cyc==13) begin s.itoa(123); end - else if (cyc==10) begin + else if (cyc==14) begin `checks(s, "123"); s.hextoa(123); end - else if (cyc==11) begin + else if (cyc==15) begin `checks(s, "7b"); s.octtoa(123); end - else if (cyc==12) begin + else if (cyc==16) begin `checks(s, "173"); s.bintoa(123); end - else if (cyc==13) begin + else if (cyc==17) begin `checks(s, "1111011"); s.realtoa(1.23); end - else if (cyc==14) begin + else if (cyc==18) begin `checks(s, "1.23"); end else if (cyc==99) begin