From e4cc6cadca63b73202fb2404ec356dd8d160de4f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 2 Oct 2026 15:20:41 +0200 Subject: [PATCH] Fix %m in hierarchical models (#8575) The model of a hierarchical block, or of a --lib-create library, is named after the %m of its instance in the parent model, but %m within it also used to include the name of its top module (l2-name within the partition), adding an extra level per block. %m now matches across non-hierarchical, --hierarchical and manual --lib-create builds. before: top.t.u_sub.sub.u_leaf.leaf after: top.t.u_sub.u_leaf --- src/V3Scope.cpp | 14 +++++++- src/V3TraceDecl.cpp | 10 +++++- test_regress/t/t_hier_scope_name.out | 6 ++++ test_regress/t/t_hier_scope_name.py | 53 +++++++++++++++++++++++++++ test_regress/t/t_hier_scope_name.v | 54 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 test_regress/t/t_hier_scope_name.out create mode 100755 test_regress/t/t_hier_scope_name.py create mode 100644 test_regress/t/t_hier_scope_name.v diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp index f14e3636c..44d799e71 100644 --- a/src/V3Scope.cpp +++ b/src/V3Scope.cpp @@ -57,6 +57,8 @@ class ScopeVisitor final : public VNVisitor { AstCell* m_aboveCellp = nullptr; // Cell that instantiates this module AstScope* m_aboveScopep = nullptr; // Scope that instantiates this scope bool m_last = false; // Scoping the last instantiation of the current module + // The name length of the scope of the library top module instance, while within it + size_t m_libTopNameLen = 0; std::unordered_map m_classOrPackageScopes; // Scopes for each class or package @@ -136,12 +138,18 @@ class ScopeVisitor final : public VNVisitor { cleanupVarRefs(); } void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_libTopNameLen); // Create required blocks and add to module string scopename; if (!m_aboveScopep) { scopename = "TOP"; } else { scopename = m_aboveScopep->name() + "." + m_aboveCellp->name(); + // The top module is the only module directly under 'TOP' + if (!v3Global.opt.libCreate().empty() && m_aboveScopep->isTop() + && VN_IS(nodep, Module)) { + m_libTopNameLen = scopename.size(); + } } UINFO(4, " MOD AT " << scopename << " " << nodep); @@ -318,7 +326,11 @@ class ScopeVisitor final : public VNVisitor { } void visit(AstScopeName* nodep) override { // If there's a %m in the display text, we add a special node that will contain the name() - const string prefix = "__DOT__"s + m_scopep->name(); + std::string scopeName = m_scopep->name(); + // Drop the "l2-name" (name of the top module) for library builds, otherwise + // would get "top.u_lib.lib.foo" instead of correct hierarchical path "top.u_lib.foo" + if (m_libTopNameLen) scopeName = "TOP" + scopeName.substr(m_libTopNameLen); + const string prefix = "__DOT__"s + scopeName; // TOP and above will be the user's name(). // Note 'TOP.' is stripped by scopePrettyName // To keep correct visual order, must add before existing diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 923a5cd81..20c23bddd 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -391,13 +391,21 @@ class TraceDeclVisitor final : public VNVisitor { void fixupLibStub(const std::string& path, AstNodeStmt* placeholderp) { FileLine* const flp = placeholderp->fileline(); + // The library instance's model is named after its %m within the parent model, + // which does not include the "l2-name" (top module) of the library + std::string name = AstNode::prettyName(path); + if (!v3Global.opt.libCreate().empty()) { + const std::string topPrefix = v3Global.rootp()->traceLibTopName() + "."; + if (VString::startsWith(name, topPrefix)) name = name.substr(topPrefix.size()); + } + // Call the initialization function for the library instance AstCStmt* const initp = new AstCStmt{flp}; initp->add("{\n"); initp->add("std::string __VlibName = vlSymsp->name();\n"); initp->add("if (!__VlibName.empty()) __VlibName += '.';\n"); initp->add("__VlibName += "); - initp->add(new AstConst{flp, AstConst::String{}, AstNode::prettyName(path)}); + initp->add(new AstConst{flp, AstConst::String{}, name}); initp->add(";\n"); initp->add("tracep->initLib(__VlibName);\n"); initp->add("}\n"); diff --git a/test_regress/t/t_hier_scope_name.out b/test_regress/t/t_hier_scope_name.out new file mode 100644 index 000000000..7a8f8fd12 --- /dev/null +++ b/test_regress/t/t_hier_scope_name.out @@ -0,0 +1,6 @@ +[1] sub: top.t.u_sub +[2] sub begin: top.t.u_sub.blk +[3] sub task: top.t.u_sub.show +[4] leaf: top.t.u_sub.u_leaf +[5] leaf begin: top.t.u_sub.u_leaf.blk +*-* All Finished *-* diff --git a/test_regress/t/t_hier_scope_name.py b/test_regress/t/t_hier_scope_name.py new file mode 100755 index 000000000..57378d7d1 --- /dev/null +++ b/test_regress/t/t_hier_scope_name.py @@ -0,0 +1,53 @@ +#!/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('vlt_all') + +# Build 3 flavours: flat, --hierarchical, manual --lib-create, all should be the same + + +def lib_create(name, extra_flags): + test.vm_prefix = "Vlib_" + name + # Single threaded, as the library is evaluated within the threads of the model using it + test.compile(make_main=False, + threads=1, + verilator_make_gmake=False, + verilator_flags2=extra_flags + ["--lib-create", name, "--top-module", name]) + test.run(logfile=test.obj_dir + "/" + test.vm_prefix + "_make.log", + cmd=[ + os.environ["MAKE"], "-C", test.obj_dir, "-f", test.vm_prefix + ".mk", + "lib" + name + ".a" + ]) + + +lib_create("leaf", []) +lib_create("sub", ["+define+USE_LIB_LEAF", test.obj_dir + "/leaf.sv", "libleaf.a"]) + + +def compile_model(prefix, extra_flags): + test.vm_prefix = prefix + # Each model needs its own main, as they share the object directory + test.main_filename = test.obj_dir + "/" + test.vm_prefix + "__main.cpp" + test.compile(verilator_flags2=extra_flags) + + +compile_model("Vnonh", []) +compile_model("Vhier", ["--hierarchical"]) +compile_model("Vlibs", [ + "+define+USE_LIB_LEAF", "+define+USE_LIB_SUB", test.obj_dir + "/sub.sv", "libsub.a", + "libleaf.a" +]) + +# Hierarchical, non-hierarchical and library builds must all print the same %m +for prefix in ("Vnonh", "Vhier", "Vlibs"): + test.execute(executable=test.obj_dir + "/" + prefix, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_hier_scope_name.v b/test_regress/t/t_hier_scope_name.v new file mode 100644 index 000000000..c1741f12c --- /dev/null +++ b/test_regress/t/t_hier_scope_name.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + int cyc = 0; + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 9) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + sub u_sub (.clk(clk), .cyc(cyc)); +endmodule + +`ifndef USE_LIB_SUB +module sub ( + input clk, + input int cyc +); + /*verilator hier_block*/ + task automatic show(); + $display("[%0d] sub task: %m", cyc); + endtask + always @(posedge clk) begin + if (cyc == 1) $display("[%0d] sub: %m", cyc); + if (cyc == 2) begin : blk + $display("[%0d] sub begin: %m", cyc); + end + if (cyc == 3) show(); + end + leaf u_leaf (.clk(clk), .cyc(cyc)); +endmodule +`endif + +`ifndef USE_LIB_LEAF +module leaf ( + input clk, + input int cyc +); + /*verilator hier_block*/ + always @(posedge clk) begin + if (cyc == 4) $display("[%0d] leaf: %m", cyc); + if (cyc == 5) begin : blk + $display("[%0d] leaf begin: %m", cyc); + end + end +endmodule +`endif