diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 6b9b19bf6..8e121aa5e 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -239,6 +239,7 @@ class AstNodeUOrStructDType VL_NOT_FINAL : public AstNodeDType { bool m_packed; bool m_isFourstate = false; // V3Width computes; true if any member is 4-state bool m_constrainedRand = false; // True if struct has constraint expression + bool m_emitToString = false; // Generate to_string() for this struct/union if set protected: AstNodeUOrStructDType(VNType t, FileLine* fl, VSigning numericUnpack) @@ -293,6 +294,8 @@ public: void classOrPackagep(AstNodeModule* classpackagep) { m_classOrPackagep = classpackagep; } bool isConstrainedRand() const { return m_constrainedRand; } void markConstrainedRand(bool flag) { m_constrainedRand = flag; } + bool emitToString() const { return m_emitToString; } + void setEmitToString() { m_emitToString = true; } }; // === Concrete node types ===================================================== diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 62caea77a..bc2fd8541 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2663,6 +2663,7 @@ class AstClass final : public AstNodeModule { bool m_needRNG = false; // Need RNG, uses srandom/randomize bool m_useVirtualPublic = false; // Subclasses need virtual public as uses interface class bool m_virtual = false; // Virtual class + bool m_printedFrom = false; // This class is printed from i.e. is used as format arg. public: AstClass(FileLine* fl, const string& name, const string& libname) @@ -2690,6 +2691,8 @@ public: void needRNG(bool flag) { m_needRNG = flag; } bool useVirtualPublic() const { return m_useVirtualPublic; } void useVirtualPublic(bool flag) { m_useVirtualPublic = flag; } + void markPrintedFrom() { m_printedFrom = true; } + bool isPrintedFrom() const { return m_printedFrom; } // Return true if this class is an extension of base class (SLOW) // Accepts nullptrs static bool isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index f3cede99c..79d4e603f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2003,6 +2003,7 @@ void AstClass::dump(std::ostream& str) const { if (isInterfaceClass()) str << " [IFCCLS]"; if (isVirtual()) str << " [VIRT]"; if (useVirtualPublic()) str << " [VIRPUB]"; + if (isPrintedFrom()) str << " [PRINTED]"; } void AstClass::dumpJson(std::ostream& str) const { // dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file @@ -2010,6 +2011,7 @@ void AstClass::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isExtended); dumpJsonBoolFuncIf(str, isInterfaceClass); dumpJsonBoolFuncIf(str, isVirtual); + dumpJsonBoolFuncIf(str, isPrintedFrom); if (baseOverride().isAny()) dumpJsonStr(str, "baseOverride", baseOverride().ascii()); dumpJsonGen(str); } @@ -2608,10 +2610,12 @@ void AstNodeUOrStructDType::dump(std::ostream& str) const { if (packed()) str << " [PACKED]"; if (isFourstate()) str << " [4STATE]"; if (classOrPackagep()) str << " pkg=" << nodeAddr(classOrPackagep()); + if (emitToString()) str << " [EMITSTR]"; } void AstNodeUOrStructDType::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, packed); dumpJsonBoolFuncIf(str, isFourstate); + dumpJsonBoolFuncIf(str, emitToString); dumpJsonGen(str); } void AstUnionDType::dump(std::ostream& str) const { diff --git a/src/V3Common.cpp b/src/V3Common.cpp index 1d0deb257..abfb9b3f7 100644 --- a/src/V3Common.cpp +++ b/src/V3Common.cpp @@ -16,7 +16,16 @@ // V3Common's Transformations: // // Each class: -// Create string access functions +// Emit to_string() if given class is printed from (used as format arg), or any of its +// parents/children is printed from, or if any of its children has another parent that is +// printed from. If class emits to_string(), emit to_string() for its struct/union/interface +// fields. +// Each struct/union: +// Emit to_string() if given struct/union is used as format arg or is a field of a +// class/struct/union that emits to_string(). If struct/union emits to_string(), emit +// to_string() for its struct/union fields. +// Each interface: +// Emit to_string() if given interface is a field of a class that emits to_string() // //************************************************************************* @@ -150,30 +159,192 @@ static void makeToStringMiddle(AstClass* nodep) { nodep->addStmtsp(funcp); } +class ToStringVisitor final : public VNVisitorConst { + // NODE STATE + // AstNode::user1() -> bool. to_string() was emitted + // AstClass::user2() -> PrintedFromParent. Used to propagate emission of to_string() from + // parent to child + const VNUser1InUse m_inuser1; + const VNUser2InUse m_inuser2; + size_t m_emitTowardsRoot + = 0; // class that require its ancestors to emit to_string() sets this + size_t m_emitForMembers = 0; // class/struct that requires its members (and their members + // recursively) to emit to_string() sets this + enum class PrintedFromParent : uint8_t { + UNRESOLVED, + PRINTED_FROM_PARENT, + NO_PRINTED_FROM_PARENT + }; + + VDouble0 m_statClassToString; + VDouble0 m_statInterfaceToString; + VDouble0 m_statStructUnionToString; + + void markClassIterate(AstClass* classp) { + if (classp->user1SetOnce()) return; + VL_RESTORER(m_emitForMembers); + ++m_emitForMembers; + for (AstNode* stmtp = classp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (AstVar* const varp = VN_CAST(stmtp, Var)) { + AstNodeDType* nodeDtypep = varp->dtypep(); + if (AstIfaceRefDType* const ifaceRedDTypep = VN_CAST(nodeDtypep, IfaceRefDType)) { + if (ifaceRedDTypep->ifacep()) iterateConst(ifaceRedDTypep->ifacep()); + } else { + while (nodeDtypep && nodeDtypep->subDTypep() + && nodeDtypep->subDTypep()->skipRefp()) { + nodeDtypep = nodeDtypep->subDTypep()->skipRefp(); + if (AstIfaceRefDType* const ifaceRedDTypep + = VN_CAST(nodeDtypep, IfaceRefDType)) { + if (ifaceRedDTypep->ifacep()) { + iterateConst(ifaceRedDTypep->ifacep()); + } + } else if (AstNodeUOrStructDType* const uOrStructDTypep + = VN_CAST(nodeDtypep, NodeUOrStructDType)) { + iterateConst(uOrStructDTypep); + } + } + } + } + } + + makeToString(classp); + makeToStringMiddle(classp); + ++m_statClassToString; + } + void visit(AstNodeUOrStructDType* structp) override { + if (structp->user1()) + return; // if to_string() was already emitted skip the rest of the function + if (structp->emitToString()) ++m_emitForMembers; + if (m_emitForMembers + > 0) { // either this struct directly requires emission of to_string() or the + // class/struct that has this struct as a field requires it to emit to_string() + for (AstMemberDType* memberp = structp->membersp(); memberp; + memberp = VN_AS(memberp->nextp(), MemberDType)) { + AstNodeDType* nodeDtypep = memberp->dtypep(); + if (AstNodeUOrStructDType* const uOrStructDTypep + = VN_CAST(nodeDtypep, NodeUOrStructDType)) { + iterateConst(uOrStructDTypep); + } else { + while (nodeDtypep && nodeDtypep->subDTypep()) { + nodeDtypep = nodeDtypep->subDTypep()->skipRefp(); + if (AstNodeUOrStructDType* const uOrStructDTypep + = VN_CAST(nodeDtypep, NodeUOrStructDType)) { + iterateConst(uOrStructDTypep); + } + } + } + } + if (structp->emitToString()) --m_emitForMembers; + + if (!structp->packed()) { + makeVlToString(structp); + ++m_statStructUnionToString; + } + + structp->user1(true); // mark that to_string() was emitted to not emit it twice (if + // e.g two printed classes have this struct as a field) + } + } + void visit(AstIface* ifacep) override { + if (ifacep->user1()) + return; // if to_string() was already emitted skip the rest of the function + if (m_emitForMembers + > 0) { // class that has this iface as a field requires it to emit to_string() + makeVlToString(ifacep); + ++m_statInterfaceToString; + ifacep->user1(true); // mark that to_string() was emitted to not emit it twice (if e.g + // two printed classes have this interface as a field) + } + } + // go towards class hierarchy root, if there is any printed from class on the way, mark its + // ancestors, and mark everything on the return path from the printed from class + void visit(AstClass* classp) override { + if (classp->user1()) return; + if (classp->isPrintedFrom()) { + markClassIterate(classp); + VL_RESTORER(m_emitTowardsRoot); + ++m_emitTowardsRoot; + for (const AstClassExtends* extendp = classp->extendsp(); extendp; + extendp = VN_AS(extendp->nextp(), ClassExtends)) { + if (extendp->classp() && !extendp->classp()->user1()) { + iterateConst(extendp->classp()); + } + } + classp->user2(static_cast(PrintedFromParent::PRINTED_FROM_PARENT)); + } else { + PrintedFromParent printed_parent = static_cast(classp->user2()); + if (printed_parent != PrintedFromParent::UNRESOLVED && m_emitTowardsRoot == 0) { + return; + } + + if (m_emitTowardsRoot > 0) markClassIterate(classp); + + bool is_root = true; + for (const AstClassExtends* extendp = classp->extendsp(); extendp; + extendp = VN_AS(extendp->nextp(), ClassExtends)) { + if (extendp->classp()) { + if (static_cast(extendp->classp()->user2()) + == PrintedFromParent::UNRESOLVED + || (m_emitTowardsRoot > 0 && !extendp->classp()->user1())) { + iterateConst(extendp->classp()); + } + if (printed_parent != PrintedFromParent::PRINTED_FROM_PARENT) { + printed_parent + = static_cast(extendp->classp()->user2()); + } + is_root = false; + } + } + + if (is_root) printed_parent = PrintedFromParent::NO_PRINTED_FROM_PARENT; + + classp->user2(static_cast(printed_parent)); + if (printed_parent == PrintedFromParent::PRINTED_FROM_PARENT) { + markClassIterate(classp); + + // If one parent is printed from, other parent also has to emit to_string() to + // handle cases like: + // Base IFaceClass (printed from) + // V V + // Derived (this class) + VL_RESTORER(m_emitTowardsRoot); + ++m_emitTowardsRoot; + for (const AstClassExtends* extendp = classp->extendsp(); extendp; + extendp = VN_AS(extendp->nextp(), ClassExtends)) { + if (extendp->classp() && !extendp->classp()->user1()) { + iterateConst(extendp->classp()); + } + } + } + } + } + void visit(AstConstPool*) override {} // Accelerate + void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + +public: + explicit ToStringVisitor(AstNode* nodep) { + if (v3Global.hasPrintedObjects()) { + AstNode::user1ClearTree(); + AstNode::user2ClearTree(); + + iterateConst(nodep); + } + + V3Stats::addStat("Optimizations, Class ToString emitted", m_statClassToString); + V3Stats::addStat("Optimizations, Interface ToString emitted", m_statInterfaceToString); + V3Stats::addStat("Optimizations, Struct/union ToString emitted", + m_statStructUnionToString); + } + ~ToStringVisitor() override = default; +}; + //###################################################################### // V3Common class functions void V3Common::commonAll() { UINFO(2, __FUNCTION__ << ":"); - // NODE STATE - // Entire netlist: - // AstClass::user1() -> bool. True if class needs to_string dumper - const VNUser1InUse m_inuser1; - // Create common contents for each module - for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) { - if (AstClass* const classp = VN_CAST(nodep, Class)) { - // Create ToString methods - makeToString(classp); - makeToStringMiddle(classp); - } else if (AstIface* const ifacep = VN_CAST(nodep, Iface)) { - makeVlToString(ifacep); - } - } - for (AstNode* nodep = v3Global.rootp()->typeTablep()->typesp(); nodep; - nodep = nodep->nextp()) { - if (AstNodeUOrStructDType* const dtypep = VN_CAST(nodep, NodeUOrStructDType)) { - if (!dtypep->packed()) makeVlToString(dtypep); - } - } + + ToStringVisitor{v3Global.rootp()}; + V3Global::dumpCheckGlobalTree("common", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3Global.h b/src/V3Global.h index 52f4af3f6..c4af01f87 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -133,6 +133,7 @@ class V3Global final { bool m_useParallelBuild = false; // Use parallel build for model bool m_useRandSequence = false; // Has `randsequence` bool m_useRandomizeMethods = false; // Need to define randomize() class methods + bool m_hasPrintedObjects = false; // Design has format args printed with to_string() uint64_t m_currentHierBlockCost = 0; // Total cost of this hier block, used for scheduling // Memory address to short string mapping (for debug) @@ -221,6 +222,8 @@ public: void useRandSequence(bool flag) { m_useRandSequence = flag; } bool useRandomizeMethods() const { return m_useRandomizeMethods; } void useRandomizeMethods(bool flag) { m_useRandomizeMethods = flag; } + bool hasPrintedObjects() const { return m_hasPrintedObjects; } + void hasPrintedObjects(bool flag) { m_hasPrintedObjects = flag; } void saveJsonPtrFieldName(const std::string& fieldName); void ptrNamesDumpJson(std::ostream& os); void idPtrMapDumpJson(std::ostream& os); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 036a95ae6..5aedf9202 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -6415,6 +6415,26 @@ class WidthVisitor final : public VNVisitor { } else if (dtypep->isString()) { formatAttr = VFormatAttr::STRING; } else if (isFormatNonNumericArg(dtypep)) { + if (AstVarRef* const varRefp = VN_CAST(argp, VarRef)) { + if (AstClassRefDType* const classRefp + = VN_CAST(varRefp->dtypep(), ClassRefDType)) { + if (classRefp->classp()) { + classRefp->classp()->markPrintedFrom(); + v3Global.hasPrintedObjects(true); + } + } else { + AstNodeDType* nodeDtypep = varRefp->dtypep(); + while (nodeDtypep && nodeDtypep->subDTypep() + && nodeDtypep->subDTypep()->skipRefp()) { + nodeDtypep = nodeDtypep->subDTypep()->skipRefp(); + if (AstNodeUOrStructDType* const uOrStructDTypep + = VN_CAST(nodeDtypep, NodeUOrStructDType)) { + uOrStructDTypep->setEmitToString(); + v3Global.hasPrintedObjects(true); + } + } + } + } AstNodeExpr* const newp = new AstToStringN{argp->fileline(), argp}; formatAttr = VFormatAttr::COMPLEX; argp = newp; diff --git a/test_regress/t/t_to_string_emitted.out b/test_regress/t/t_to_string_emitted.out new file mode 100644 index 000000000..4b07c33a7 --- /dev/null +++ b/test_regress/t/t_to_string_emitted.out @@ -0,0 +1,9 @@ +struct: ''{s1:'{a:'h0}, s2:'{'{'{b:'h0}, '{b:'h0}, '{b:'h0}}, '{'{b:'h0}, '{b:'h0}, '{b:'h0}}}, x:'h0}' +class: ''{z:'h0, i1:top.t.i1, i2:'{'{top.t.i2}}, s1:'{a:'h0}, s2:'{'{'{b:'h0}, '{b:'h0}, '{b:'h0}}, '{'{b:'h0}, '{b:'h0}, '{b:'h0}}}}' +class from subclass: ''{derived_a:'h0, base_a:'h0}' +class from superclass: ''{derived2_a:'h0, c2:$unit::Class2, base2_a:'h0}' +class from interface: ''{derived3_a:'h0, base3_a:'h0}' +class from interface 2: ''{derived4_a:'h0, }' +nested class: ''{nested_class_field:'h0}' +classes with shared field types: ''{s1:'{x:'h0}, s2:'{x:'h0}, i:top.t.i3}', ''{s:'{x:'h0}, i:top.t.i3}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted.py b/test_regress/t/t_to_string_emitted.py new file mode 100755 index 000000000..2e9d00c5c --- /dev/null +++ b/test_regress/t/t_to_string_emitted.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats", "-DDISPLAY_OBJECTS"]) + +test.execute() + +test.file_grep(test.stats, r"Optimizations, Struct/union ToString emitted\s+(\d+)", 6) +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", 13) +test.file_grep(test.stats, r"Optimizations, Interface ToString emitted\s+(\d+)", 3) + +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted.v b/test_regress/t/t_to_string_emitted.v new file mode 100644 index 000000000..a5c98a574 --- /dev/null +++ b/test_regress/t/t_to_string_emitted.v @@ -0,0 +1,150 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +typedef struct { + struct { + int a; + } s1; + struct { + int b; + } s2[2][3]; + int x; +} struct_t; + +interface iface; + int y1; +endinterface + +interface iface2; + int y2; +endinterface + +class Class; + int z; + virtual iface i1; + virtual iface2 i2[1][1]; + struct { + int a; + } s1; + struct { + int b; + } s2[2][3]; +endclass + +class Class2; + int x; +endclass; + +class Base; + int base_a; +endclass + +class Derived extends Base; + int derived_a; +endclass + +class Base2; + int base2_a; +endclass + +class Derived2 extends Base2; + int derived2_a; + Class2 c2; +endclass; + +class Base3; + int base3_a; +endclass + +interface class Iclass; +endclass + +class Derived3 extends Base3 implements Iclass; + int derived3_a; +endclass + +interface class Iclass2; +endclass + +class Derived4 implements Iclass2; + int derived4_a; +endclass + +class OuterClass; + class NestedClass; + int nested_class_field; + endclass +endclass + +typedef struct { + int x; +} struct2_t; + +interface iface3; + int i; +endinterface + +class Class3; + struct2_t s1; + struct2_t s2; + virtual iface3 i; +endclass + +class Class4; + struct2_t s; + virtual iface3 i; +endclass + +module t; + struct_t s; + iface i1(); + iface2 i2(); + Class c; + Derived d; + Derived2 d2; + Base2 b2; + Derived3 d3; + Iclass iclass; + Derived4 d4; + Iclass2 iclass2; + OuterClass::NestedClass nc; + Class3 c3; + Class4 c4; + iface3 i3(); + + initial begin + c = new; + c.i1 = i1; + c.i2[0][0] = i2; + d = new; + d2 = new; + d2.c2 = new; + b2 = d2; + d3 = new; + iclass = d3; + d4 = new; + iclass2 = d4; + nc = new; + c3 = new; + c3.i = i3; + c4 = new; + c4.i = i3; + +`ifdef DISPLAY_OBJECTS + $display("struct: '%p'", s); + $display("class: '%p'", c); + $display("class from subclass: '%p'", d); + $display("class from superclass: '%p'", b2); + $display("class from interface: '%p'", iclass); + $display("class from interface 2: '%p'", d4); + $display("nested class: '%p'", nc); + $display("classes with shared field types: '%p', '%p'", c3, c4); +`endif + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy.v b/test_regress/t/t_to_string_emitted_class_hierarchy.v new file mode 100644 index 000000000..3f074a716 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Base +// V +// Derived1---------------- IClass1 +// V V V +// Derived2------ Derived7 +// V V V +// Derived3 Derived5 Derived8 +// V V V +// Derived4 Derived6 Derived9 + +interface class IClass1; +endclass + +class Base; + int base_field; +endclass + +class Derived1 extends Base; + int derived1_field; +endclass + +class Derived2 extends Derived1; + int derived2_field; +endclass + +class Derived3 extends Derived2; + int derived3_field; +endclass + +class Derived4 extends Derived3; + int derived4_field; +endclass + +class Derived5 extends Derived2; + int derived5_field; +endclass + +class Derived6 extends Derived5; + int derived6_field; +endclass + +class Derived7 extends Derived1 implements IClass1; + int derived7_field; +endclass + +class Derived8 extends Derived7; + int derived8_field; +endclass + +class Derived9 extends Derived8; + int derived9_field; +endclass + +module t; + `OBJ_TYPE obj = new; + `PRINTED_FROM_TYPE printed_obj = obj; +`ifdef OBJ_TYPE2 + `OBJ_TYPE2 obj2 = new; + `PRINTED_FROM_TYPE2 printed_obj2 = obj2; +`endif + + initial begin + $display("'%p'", printed_obj); +`ifdef OBJ_TYPE2 + $display("'%p'", printed_obj2); +`endif + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy1.out b/test_regress/t/t_to_string_emitted_class_hierarchy1.out new file mode 100644 index 000000000..5e65c242b --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy1.out @@ -0,0 +1,2 @@ +''{derived9_field:'h0, derived8_field:'h0, derived7_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy1.py b/test_regress/t/t_to_string_emitted_class_hierarchy1.py new file mode 100755 index 000000000..29f4a0c23 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy1.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived9" +printed_from_type = "IClass1" +expected_to_string_classes = ["Base", "Derived1", "IClass1", "Derived7", "Derived8", "Derived9"] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy2.out b/test_regress/t/t_to_string_emitted_class_hierarchy2.out new file mode 100644 index 000000000..b565f115e --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy2.out @@ -0,0 +1,2 @@ +''{derived2_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy2.py b/test_regress/t/t_to_string_emitted_class_hierarchy2.py new file mode 100755 index 000000000..253f9302f --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy2.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived2" +printed_from_type = "Derived2" +expected_to_string_classes = [ + "Base", "Derived1", "Derived2", "Derived3", "Derived4", "Derived5", "Derived6" +] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy3.out b/test_regress/t/t_to_string_emitted_class_hierarchy3.out new file mode 100644 index 000000000..ef7a40df1 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy3.out @@ -0,0 +1,2 @@ +''{derived3_field:'h0, derived2_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy3.py b/test_regress/t/t_to_string_emitted_class_hierarchy3.py new file mode 100755 index 000000000..89bd6a748 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy3.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived3" +printed_from_type = "Derived3" +expected_to_string_classes = ["Base", "Derived1", "Derived2", "Derived3", "Derived4"] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy4.out b/test_regress/t/t_to_string_emitted_class_hierarchy4.out new file mode 100644 index 000000000..9acbae300 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy4.out @@ -0,0 +1,2 @@ +''{derived6_field:'h0, derived5_field:'h0, derived2_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy4.py b/test_regress/t/t_to_string_emitted_class_hierarchy4.py new file mode 100755 index 000000000..95bbcc9a9 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy4.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived6" +printed_from_type = "Derived6" +expected_to_string_classes = ["Base", "Derived1", "Derived2", "Derived5", "Derived6"] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy5.out b/test_regress/t/t_to_string_emitted_class_hierarchy5.out new file mode 100644 index 000000000..1a60e850e --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy5.out @@ -0,0 +1,3 @@ +''{derived7_field:'h0, derived1_field:'h0, base_field:'h0}' +''{derived9_field:'h0, derived8_field:'h0, derived7_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy5.py b/test_regress/t/t_to_string_emitted_class_hierarchy5.py new file mode 100755 index 000000000..53e9d1115 --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy5.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived7" +printed_from_type = "Derived7" +obj_type2 = "Derived9" +printed_from_type2 = "Derived9" +expected_to_string_classes = ["Base", "Derived1", "IClass1", "Derived7", "Derived8", "Derived9"] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}", + f"-DOBJ_TYPE2={obj_type2}", f"-DPRINTED_FROM_TYPE2={printed_from_type2}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy6.out b/test_regress/t/t_to_string_emitted_class_hierarchy6.out new file mode 100644 index 000000000..95efb82de --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy6.out @@ -0,0 +1,3 @@ +''{derived4_field:'h0, derived3_field:'h0, derived2_field:'h0, derived1_field:'h0, base_field:'h0}' +''{derived9_field:'h0, derived8_field:'h0, derived7_field:'h0, derived1_field:'h0, base_field:'h0}' +*-* All Finished *-* diff --git a/test_regress/t/t_to_string_emitted_class_hierarchy6.py b/test_regress/t/t_to_string_emitted_class_hierarchy6.py new file mode 100755 index 000000000..d4d0a6bee --- /dev/null +++ b/test_regress/t/t_to_string_emitted_class_hierarchy6.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_to_string_emitted_class_hierarchy.v" + +obj_type = "Derived4" +printed_from_type = "Derived4" +obj_type2 = "Derived9" +printed_from_type2 = "Derived9" +expected_to_string_classes = [ + "Base", "Derived1", "Derived2", "Derived3", "Derived4", "IClass1", "Derived7", "Derived8", + "Derived9" +] + +test.compile(verilator_flags2=[ + "--stats", f"-DOBJ_TYPE={obj_type}", f"-DPRINTED_FROM_TYPE={printed_from_type}", + f"-DOBJ_TYPE2={obj_type2}", f"-DPRINTED_FROM_TYPE2={printed_from_type2}" +]) +for class_name in expected_to_string_classes: + test.file_grep( + f"{test.obj_dir}/{test.vm_prefix}___024unit__03a__03a{class_name}__Vclpkg__0.cpp", + f"std::string {test.vm_prefix}___024unit__03a__03a{class_name}::to_string()") +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", + len(expected_to_string_classes)) +test.execute() +test.files_identical(test.obj_dir + "/vlt_sim.log", test.golden_filename, "logfile") + +test.passes() diff --git a/test_regress/t/t_to_string_emitted_not.py b/test_regress/t/t_to_string_emitted_not.py new file mode 100755 index 000000000..f59263d4a --- /dev/null +++ b/test_regress/t/t_to_string_emitted_not.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t_to_string_emitted.v" + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r"Optimizations, Struct/union ToString emitted\s+(\d+)", 0) +test.file_grep(test.stats, r"Optimizations, Class ToString emitted\s+(\d+)", 0) +test.file_grep(test.stats, r"Optimizations, Interface ToString emitted\s+(\d+)", 0) + +test.passes()