diff --git a/include/verilated_trace.h b/include/verilated_trace.h index 15e3cd964..93d4fc1a0 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -299,6 +299,7 @@ protected: private: std::vector m_sigs_enabledVec; // Staging for m_sigs_enabledp std::vector m_initCbs; // Routines to initialize tracing + std::vector m_initCbsCalled; // Init callbacks already run for this open std::vector m_constCbs; // Routines to perform const dump std::vector m_constOffloadCbs; // Routines to perform offloaded const dump std::vector m_fullCbs; // Routines to perform full dump @@ -312,6 +313,7 @@ private: uint32_t m_numSignals = 0; // Number of distinct signals uint32_t m_maxBits = 0; // Number of bits in the widest signal void* m_initUserp = nullptr; // The callback userp of the instance currently being initialized + bool m_rootInit = true; // Whether the current init callback was reached from the root // TODO: Should keep this as a Trie, that is how it's accessed all the time. std::vector> m_dumpvars; // dumpvar() entries double m_timeRes = 1e-9; // Time resolution (ns/ms etc) @@ -328,6 +330,7 @@ private: // to access duck-typed functions to avoid a virtual function call. T_Trace* self() { return static_cast(this); } + void runInitCallback(size_t index, bool rootInit) VL_MT_UNSAFE; void runCallbacks(const std::vector& cbVec); void runOffloadedCallbacks(const std::vector& cbVec); @@ -449,6 +452,7 @@ public: //========================================================================= // Non-hot path internal interface to Verilator generated code + bool rootInit() const VL_MT_UNSAFE { return m_rootInit; } void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); void addInitCb(initCb_t cb, void* userp, const std::string& name, bool isLibInstance, uint32_t nTraceCodes) VL_MT_SAFE; diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index 635e3e897..bb13a1476 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -301,6 +301,25 @@ VerilatedTrace::~VerilatedTrace() { //========================================================================= // Internals available to format-specific implementations +template <> +void VerilatedTrace::runInitCallback(size_t index, + bool rootInit) VL_MT_UNSAFE { + if (m_initCbsCalled[index]) return; + + const CallbackRecord& cbr = m_initCbs[index]; + const uint32_t baseCode = nextCode(); + m_nextCode += cbr.m_nTraceCodes; + + void* const prevInitUserp = m_initUserp; + const bool prevRootInit = m_rootInit; + m_initUserp = cbr.m_userp; + m_rootInit = rootInit; + cbr.m_initCb(cbr.m_userp, self(), baseCode); + m_initUserp = prevInitUserp; + m_rootInit = prevRootInit; + m_initCbsCalled[index] = true; +} + template <> void VerilatedTrace::traceInit() VL_MT_UNSAFE { // Note: It is possible to re-open a trace file (VCD in particular), @@ -311,18 +330,13 @@ void VerilatedTrace::traceInit() VL_MT_UNSAFE { m_numSignals = 0; m_maxBits = 0; m_sigs_enabledVec.clear(); + m_initCbsCalled.assign(m_initCbs.size(), false); - // Call all initialize callbacks for root (non-library) instances, which will: + // Call all initialize callbacks for root instances, which will: // - Call decl* for each signal (these eventually call ::declCode) // - Call the initialize callbacks of library instances underneath // - Store the base code - for (const CallbackRecord& cbr : m_initCbs) { - if (cbr.m_isLibInstance) continue; // Will be called from parent callback - const uint32_t baseCode = nextCode(); - m_nextCode += cbr.m_nTraceCodes; - m_initUserp = cbr.m_userp; - cbr.m_initCb(cbr.m_userp, self(), baseCode); - } + for (size_t i = 0; i < m_initCbs.size(); ++i) runInitCallback(i, true); if (expectedCodes && nextCode() != expectedCodes) { VL_FATAL_MT(__FILE__, __LINE__, "", @@ -728,14 +742,9 @@ void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* user template <> void VerilatedTrace::initLib(const std::string& name) VL_MT_SAFE { // Note it's possible the instance doesn't exist if the lib was compiled without tracing - void* const prevInitUserp = m_initUserp; - for (const CallbackRecord& cbr : m_initCbs) { - if (cbr.m_name != name) continue; - const uint32_t baseCode = nextCode(); - m_nextCode += cbr.m_nTraceCodes; - m_initUserp = cbr.m_userp; - cbr.m_initCb(cbr.m_userp, self(), baseCode); - m_initUserp = prevInitUserp; + for (size_t i = 0; i < m_initCbs.size(); ++i) { + if (m_initCbs[i].m_name != name) continue; + runInitCallback(i, false); } } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 0c2632604..62d3f5b18 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1271,6 +1271,7 @@ class AstNetlist final : public AstNode { // @astgen ptr := m_stlFirstIterationp: Optional[AstVarScope] // Settle first iteration flag VTimescale m_timeunit; // Global time unit VTimescale m_timeprecision; // Global time precision + std::string m_resolvedTopModuleName; // Selected design top before wrapping under $root bool m_timescaleSpecified = false; // Input HDL specified timescale uint32_t m_nTraceCodes = 0; // Number of trace codes used by design public: @@ -1314,10 +1315,16 @@ public: void timeprecisionMerge(FileLine*, const VTimescale& value); void timescaleSpecified(bool specified) { m_timescaleSpecified = specified; } bool timescaleSpecified() const { return m_timescaleSpecified; } + const std::string& resolvedTopModuleName() const { return m_resolvedTopModuleName; } + void resolvedTopModuleName(const std::string& value) { m_resolvedTopModuleName = value; } uint32_t nTraceCodes() const { return m_nTraceCodes; } void nTraceCodes(uint32_t value) { m_nTraceCodes = value; } AstVarScope* stlFirstIterationp(); void clearStlFirstIterationp() { m_stlFirstIterationp = nullptr; } + const std::string traceLibTopName() const { + const std::string& name = resolvedTopModuleName(); + return prettyName(name.empty() ? v3Global.rootp()->topModulep()->name() : name); + } }; class AstPackageExport final : public AstNode { // A package export declaration diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 340c0587a..365fa86f2 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -528,10 +528,17 @@ class EmitCModel final : public EmitCFunc { void emitTraceMethods(AstNodeModule* modp) { const string topModNameProtected = EmitCUtil::prefixNameProtect(modp); + const string topTraceName + = V3OutFormatter::quoteNameControls(v3Global.rootp()->traceLibTopName()); putSectionDelimiter("Trace configuration"); // Forward declaration + if (!v3Global.opt.libCreate().empty()) { + putns(modp, "\nvoid " + topModNameProtected + "__" + protect("trace_init_root") + "(" + + topModNameProtected + "* vlSelf, " + v3Global.opt.traceClassBase() + + "* tracep);\n"); + } putns(modp, "\nvoid " + topModNameProtected + "__" + protect("trace_decl_types") + "(" + v3Global.opt.traceClassBase() + "* tracep);\n"); putns(modp, "\nvoid " + topModNameProtected + "__" + protect("trace_init_top") + "(" @@ -553,11 +560,23 @@ class EmitCModel final : public EmitCFunc { puts("vlSymsp->__Vm_baseCode = code;\n"); if (v3Global.opt.libCreate().empty()) { puts("tracep->pushPrefix(vlSymsp->name(), VerilatedTracePrefixType::SCOPE_MODULE);\n"); + } else { + puts("if (tracep->rootInit()) {\n"); + puts("tracep->pushPrefix(vlSymsp->name(), VerilatedTracePrefixType::SCOPE_MODULE);\n"); + puts(topModNameProtected + "__" + protect("trace_init_root") + "(vlSelf, tracep);\n"); + puts("tracep->pushPrefix(\"" + topTraceName + + "\", VerilatedTracePrefixType::SCOPE_MODULE);\n"); + puts("}\n"); } puts(topModNameProtected + "__" + protect("trace_decl_types") + "(tracep);\n"); puts(topModNameProtected + "__" + protect("trace_init_top") + "(vlSelf, tracep);\n"); if (v3Global.opt.libCreate().empty()) { // puts("tracep->popPrefix();\n"); + } else { + puts("if (tracep->rootInit()) {\n"); + puts("tracep->popPrefix();\n"); + puts("tracep->popPrefix();\n"); + puts("}\n"); } puts("}\n"); diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index b3c7bf258..012c87166 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -167,6 +167,7 @@ void V3LinkLevel::wrapTop(AstNetlist* rootp) { UINFO(1, "No module found to wrap"); return; } + rootp->resolvedTopModuleName(oldmodp->name()); AstNodeModule* const newmodp = new AstModule{oldmodp->fileline(), "$root", oldmodp->libname()}; newmodp->name(AstNode::encodeName(newmodp->name())); // so origName is nice diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 959b9ef77..990e94403 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -197,8 +197,38 @@ class TraceVisitor final : public VNVisitor { // For activity set, what traces apply using TraceVec = std::multimap; + class TraceInitDeclCollector final : public VNVisitor { + std::vector& m_declps; + std::set m_seenFuncps; + + void visit(AstTraceDecl* nodep) override { m_declps.push_back(nodep); } + void visit(AstCCall* nodep) override { + if (AstCFunc* const funcp = nodep->funcp()) collect(funcp); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + + public: + explicit TraceInitDeclCollector(std::vector& declps) + : m_declps{declps} {} + void collect(AstCFunc* funcp) { + if (funcp && m_seenFuncps.insert(funcp).second) iterate(funcp); + } + }; + // METHODS + static bool sameRootInitAlias(const AstTraceDecl* rootDeclp, const AstTraceDecl* topDeclp) { + const VNumRange& rootBitRange = rootDeclp->bitRange(); + const VNumRange& topBitRange = topDeclp->bitRange(); + return rootDeclp->showname() == topDeclp->showname() + && rootDeclp->declDirection() == topDeclp->declDirection() + && rootDeclp->widthMin() == topDeclp->widthMin() + && rootBitRange.ranged() == topBitRange.ranged() + && (!rootBitRange.ranged() + || (rootBitRange.left() == topBitRange.left() + && rootBitRange.right() == topBitRange.right())); + } + void detectDuplicates() { UINFO(9, "Finding duplicates"); // Note uses user4 @@ -800,6 +830,32 @@ class TraceVisitor final : public VNVisitor { // Create the full and incremental dump functions createNonConstTraceFunctions(traces, nNonConstCodes, m_parallelism); + // Root-traced libraries alias wrapper IOs onto the existing top-module codes. + if (!v3Global.opt.libCreate().empty()) { + std::vector rootDeclps; + std::vector topDeclps; + TraceInitDeclCollector rootCollector{rootDeclps}; + TraceInitDeclCollector topCollector{topDeclps}; + for (AstNode* blockp = m_topScopep->blocksp(); blockp; blockp = blockp->nextp()) { + AstCFunc* const funcp = VN_CAST(blockp, CFunc); + if (funcp && VString::startsWith(funcp->name(), "trace_init_leaf_root__")) { + rootCollector.collect(funcp); + } else if (funcp && VString::startsWith(funcp->name(), "trace_init_leaf_top__")) { + topCollector.collect(funcp); + } + } + + std::vector used(topDeclps.size(), false); + for (AstTraceDecl* const rootDeclp : rootDeclps) { + for (size_t i = 0; i < topDeclps.size(); ++i) { + if (used[i] || !sameRootInitAlias(rootDeclp, topDeclps[i])) continue; + rootDeclp->code(topDeclps[i]->code()); + used[i] = true; + break; + } + } + } + // Remove refs to traced values from TraceDecl nodes, these have now moved under // TraceInc for (const auto& i : traces) { diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index d79ffc540..c9b1d58b2 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -86,7 +86,10 @@ public: // Emit Prefix adjustments to unwind the path back to its original state void unwind() { - for (unsigned toPop = m_stack.size(); --toPop;) m_emit(new AstTracePopPrefix{m_flp}); + while (m_stack.size() > 1) { + m_emit(new AstTracePopPrefix{m_flp}); + m_stack.pop_back(); + } } }; @@ -103,6 +106,8 @@ class TraceDeclVisitor final : public VNVisitor { std::set m_declUncalledps; // Declarations not called int m_topFuncSize = 0; // Size of the top function currently being built int m_subFuncSize = 0; // Size of the sub function currently being built + size_t m_topScopeRootFuncCount = 0; // Top-scope init functions used only for wrapper IOs + bool m_topScopeRootPhase = false; // Emitting top-scope wrapper IO declarations const int m_funcSizeLimit // Maximum size of a function = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace() : std::numeric_limits::max(); @@ -148,21 +153,21 @@ class TraceDeclVisitor final : public VNVisitor { m_name = vcdName.substr(pathLen); } - // When creating a --lib-create library, drop the name of the top module (l2 name). - // This will be replaced by the instance name in the model that uses the library. - // This would be a bit murky when there are other top level entities ($unit, - // packages, which have an instance in all libs - a problem on its own). If - // --top-module was explicitly specified, then we will drop the prefix only for the - // actual top level module, and wrap the rest in '$libroot'. This way at least we get a - // usable dump of everything, with library instances showing in a right place, without - // pollution from other top level entities. + // When creating a --lib-create library, drop the name of the selected top module. + // This will be replaced by the instance name in the model that uses the library, or + // restored at runtime if the library itself is traced as the root model. Other top + // level entities ($unit, packages, ...) keep a '$libroot' wrapper so they still have + // a stable location in the dump. if (inTopScope && !v3Global.opt.libCreate().empty()) { const size_t start = m_path.find(' '); // Must have a prefix in the top scope with lib, as top wrapper signals not traced UASSERT_OBJ(start != std::string::npos, nodep, "No prefix with --lib-create"); const std::string prefix = m_path.substr(0, start); + // Wrapper primary IOs stay under $rootio so a root-traced library can restore + // them under the runtime C++ instance name without affecting embedded use. + if (prefix == "$rootio") return; m_path = m_path.substr(start + 1); - if (v3Global.opt.topModule() != prefix) m_path = "$libroot " + m_path; + if (v3Global.rootp()->traceLibTopName() != prefix) m_path = "$libroot " + m_path; } } @@ -260,7 +265,11 @@ class TraceDeclVisitor final : public VNVisitor { // FileLine* const flp = m_currScopep->fileline(); const string n = cvtToStr(m_subFuncps.size()); - const string name{"trace_init_sub__" + m_currScopep->nameDotless() + "__" + n}; + const string name + = m_currScopep == m_topScopep->scopep() && !v3Global.opt.libCreate().empty() + ? (m_topScopeRootPhase ? "trace_init_leaf_root__" : "trace_init_leaf_top__") + + n + : "trace_init_sub__" + m_currScopep->nameDotless() + "__" + n; AstCFunc* const funcp = newCFunc(flp, name); funcp->addStmtsp(new AstCStmt{flp, "const int c = vlSymsp->__Vm_baseCode;"}); m_subFuncps.push_back(funcp); @@ -429,10 +438,11 @@ class TraceDeclVisitor final : public VNVisitor { m_entries.begin(), m_entries.end(), [](const TraceEntry& a, const TraceEntry& b) { return a.operatorCompare(b); }); - // Build trace initialization functions for this AstScope FileLine* const flp = nodep->fileline(); PathAdjustor pathAdjustor{flp, [&](AstNodeStmt* stmtp) { addToSubFunc(stmtp); }}; - for (const TraceEntry& entry : m_entries) { + const bool splitRootPrimaryIos = nodep->isTop() && !v3Global.opt.libCreate().empty(); + const auto emitEntry = [&](const TraceEntry& entry) { + AstVarScope* const vscp = entry.vscp(); // Adjust name prefix based on path in hierarchy UINFO(9, "path='" << entry.path() << "' name='" << entry.name() << "' " << (entry.cellp() ? static_cast(entry.cellp()) @@ -441,7 +451,7 @@ class TraceDeclVisitor final : public VNVisitor { m_traName = entry.name(); - if (AstVarScope* const vscp = entry.vscp()) { + if (vscp) { // This is a signal: build AstTraceDecl for it m_traVscp = vscp; const string& ignoreReason = vscIgnoreTrace(m_traVscp); @@ -472,6 +482,23 @@ class TraceDeclVisitor final : public VNVisitor { addToSubFunc(stmtp); m_cellInitPlaceholders.emplace_back(nodep, cellp, stmtp); } + }; + if (splitRootPrimaryIos) { + m_topScopeRootPhase = true; + for (const TraceEntry& entry : m_entries) { + AstVarScope* const vscp = entry.vscp(); + if (!(vscp && vscp->varp()->isPrimaryIO())) continue; + emitEntry(entry); + } + m_topScopeRootPhase = false; + pathAdjustor.unwind(); + m_topScopeRootFuncCount = m_subFuncps.size(); + if (m_topScopeRootFuncCount) m_subFuncSize = m_funcSizeLimit + 1; + } + for (const TraceEntry& entry : m_entries) { + AstVarScope* const vscp = entry.vscp(); + if (splitRootPrimaryIos && vscp && vscp->varp()->isPrimaryIO()) continue; + emitEntry(entry); } pathAdjustor.unwind(); m_traVscp = nullptr; @@ -532,8 +559,6 @@ class TraceDeclVisitor final : public VNVisitor { // When creating a --lib-create library ... if (!v3Global.opt.libCreate().empty()) { - // Ignore the wrapper created primary IO ports - if (nodep->varp()->isPrimaryIO()) return; // Ignore parameters in packages. These will be traced at the top level. if (nodep->varp()->isParam() && VN_IS(nodep->scopep()->modp(), Package)) return; } @@ -764,8 +789,22 @@ public: // push/pop pairs is a bit hard. It is cleaner to remove them. removeRedundantPrefixPushPop(); - // Call the initialization functions of the root scope from the top function - for (AstCFunc* const funcp : m_scopeInitFuncps.at(m_topScopep->scopep())) { + const std::vector& topScopeFuncps = m_scopeInitFuncps.at(m_topScopep->scopep()); + AstCFunc* rootFuncp = nullptr; + if (!v3Global.opt.libCreate().empty()) { + rootFuncp = newCFunc(flp, "trace_init_root"); + for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) { + AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)}; + callp->dtypeSetVoid(); + callp->argTypes("tracep"); + rootFuncp->addStmtsp(callp->makeStmt()); + } + if (!m_topScopeRootFuncCount) rootFuncp->addStmtsp(new AstComment{flp, "Empty"}); + } + + // Call the non-wrapper initialization functions of the root scope from the top function + for (size_t i = m_topScopeRootFuncCount; i < topScopeFuncps.size(); ++i) { + AstCFunc* const funcp = topScopeFuncps.at(i); AstCCall* const callp = new AstCCall{flp, funcp}; callp->dtypeSetVoid(); callp->argTypes("tracep"); @@ -792,6 +831,7 @@ public: AstCFunc* const topFuncp = m_topFuncps.front(); topFuncp->name("trace_init_top"); + if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp); checkCalls(topFuncp); } ~TraceDeclVisitor() override { diff --git a/test_regress/t/t_trace_lib_as_top.cpp b/test_regress/t/t_trace_lib_as_top.cpp new file mode 100644 index 000000000..132e7432c --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top.cpp @@ -0,0 +1,32 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// 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 + +#include + +#include VM_PREFIX_INCLUDE + +extern "C" int sim_main(int argc, char* argv[]) { + const std::unique_ptr contextp{new VerilatedContext}; + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + + contextp->debug(0); + contextp->traceEverOn(true); + contextp->commandArgs(argc, argv); + + while (!contextp->gotFinish()) { + topp->eval(); + topp->clk = !topp->clk; + contextp->timeInc(1); + } + + topp->final(); + + return 0; +} + +int main(int argc, char* argv[]) { return sim_main(argc, argv); } diff --git a/test_regress/t/t_trace_lib_as_top.v b/test_regress/t/t_trace_lib_as_top.v new file mode 100644 index 000000000..419e04c7e --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top.v @@ -0,0 +1,40 @@ +// 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 + +`define STRINGIFY(x) `"x`" + +module t( + input clk +); + + int cyc = 1; + + Factorial factorial( + .clk(clk), + .i(cyc) + ); + + initial begin + $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); + $dumpvars; + end + + always @(posedge clk) begin + cyc <= cyc+1; + if (cyc == 5) begin + $finish; + end + end +endmodule + +module Factorial( + input clk, + input int i +); + int fact = 1; + always @(posedge clk) begin + fact <= fact * i; + end +endmodule diff --git a/test_regress/t/t_trace_lib_as_top_fst.out b/test_regress/t/t_trace_lib_as_top_fst.out new file mode 100644 index 000000000..c9e408f6b --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_fst.out @@ -0,0 +1,56 @@ +$date +Thu Apr 2 18:53:18 2026 + +$end +$version +Generated by VerilatedFst +$end +$timescale +1ps +$end + $scope module top $end + $var wire 1 ! clk $end + $scope module t $end + $var wire 1 ! clk $end + $var int 32 " cyc [31:0] $end + $scope module factorial $end + $var wire 1 ! clk $end + $var wire 32 " i [31:0] $end + $var int 32 # fact [31:0] $end + $upscope $end + $upscope $end + $upscope $end +$enddefinitions $end +#0 +$dumpvars +b00000000000000000000000000000001 # +b00000000000000000000000000000001 " +0! +$end +#1 +1! +b00000000000000000000000000000010 " +#2 +0! +#3 +1! +b00000000000000000000000000000011 " +b00000000000000000000000000000010 # +#4 +0! +#5 +1! +b00000000000000000000000000000110 # +b00000000000000000000000000000100 " +#6 +0! +#7 +1! +b00000000000000000000000000000101 " +b00000000000000000000000000011000 # +#8 +0! +#9 +1! +b00000000000000000000000001111000 # +b00000000000000000000000000000110 " diff --git a/test_regress/t/t_trace_lib_as_top_fst.py b/test_regress/t/t_trace_lib_as_top_fst.py new file mode 100755 index 000000000..b42540816 --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_fst.py @@ -0,0 +1,15 @@ +#!/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 +import trace_lib_as_top_common + +test.scenarios('vlt_all') + +trace_lib_as_top_common.run(test) diff --git a/test_regress/t/t_trace_lib_as_top_saif.out b/test_regress/t/t_trace_lib_as_top_saif.out new file mode 100644 index 000000000..e89838078 --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_saif.out @@ -0,0 +1,120 @@ +// Generated by verilated_saif +(SAIFILE +(SAIFVERSION "2.0") +(DIRECTION "backward") +(PROGRAM_NAME "Verilator") +(DIVIDER / ) +(TIMESCALE 1ps) +(DURATION 9) + (INSTANCE top + (NET + (clk (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 9)) + ) + (INSTANCE t + (NET + (clk (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 9)) + (cyc\[0\] (T0 4) (T1 5) (TZ 0) (TX 0) (TB 0) (TC 6)) + (cyc\[1\] (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 3)) + (cyc\[2\] (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 1)) + (cyc\[3\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[4\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[5\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[6\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[7\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[8\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[9\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[10\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[11\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[12\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[13\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[14\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[15\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[16\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[17\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[18\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[19\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[20\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[21\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[22\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[23\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[24\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[25\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[26\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[27\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[28\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[29\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[30\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (cyc\[31\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + ) + (INSTANCE factorial + (NET + (clk (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 9)) + (i\[0\] (T0 4) (T1 5) (TZ 0) (TX 0) (TB 0) (TC 6)) + (i\[1\] (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 3)) + (i\[2\] (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 1)) + (i\[3\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[4\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[5\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[6\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[7\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[8\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[9\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[10\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[11\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[12\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[13\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[14\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[15\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[16\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[17\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[18\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[19\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[20\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[21\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[22\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[23\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[24\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[25\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[26\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[27\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[28\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[29\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[30\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (i\[31\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[0\] (T0 6) (T1 3) (TZ 0) (TX 0) (TB 0) (TC 2)) + (fact\[1\] (T0 5) (T1 4) (TZ 0) (TX 0) (TB 0) (TC 2)) + (fact\[2\] (T0 7) (T1 2) (TZ 0) (TX 0) (TB 0) (TC 2)) + (fact\[3\] (T0 7) (T1 2) (TZ 0) (TX 0) (TB 0) (TC 1)) + (fact\[4\] (T0 7) (T1 2) (TZ 0) (TX 0) (TB 0) (TC 1)) + (fact\[5\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 1)) + (fact\[6\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 1)) + (fact\[7\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[8\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[9\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[10\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[11\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[12\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[13\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[14\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[15\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[16\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[17\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[18\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[19\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[20\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[21\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[22\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[23\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[24\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[25\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[26\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[27\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[28\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[29\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[30\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + (fact\[31\] (T0 9) (T1 0) (TZ 0) (TX 0) (TB 0) (TC 0)) + ) + ) + ) + ) +) diff --git a/test_regress/t/t_trace_lib_as_top_saif.py b/test_regress/t/t_trace_lib_as_top_saif.py new file mode 100755 index 000000000..b42540816 --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_saif.py @@ -0,0 +1,15 @@ +#!/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 +import trace_lib_as_top_common + +test.scenarios('vlt_all') + +trace_lib_as_top_common.run(test) diff --git a/test_regress/t/t_trace_lib_as_top_vcd.out b/test_regress/t/t_trace_lib_as_top_vcd.out new file mode 100644 index 000000000..3f4390e4a --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_vcd.out @@ -0,0 +1,48 @@ +$version Generated by VerilatedVcd $end +$timescale 1ps $end + $scope module top $end + $var wire 1 " clk $end + $scope module t $end + $var wire 1 " clk $end + $var wire 32 # cyc [31:0] $end + $scope module factorial $end + $var wire 1 " clk $end + $var wire 32 # i [31:0] $end + $var wire 32 $ fact [31:0] $end + $upscope $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +0" +b00000000000000000000000000000001 # +b00000000000000000000000000000001 $ +#1 +1" +b00000000000000000000000000000010 # +#2 +0" +#3 +1" +b00000000000000000000000000000011 # +b00000000000000000000000000000010 $ +#4 +0" +#5 +1" +b00000000000000000000000000000100 # +b00000000000000000000000000000110 $ +#6 +0" +#7 +1" +b00000000000000000000000000000101 # +b00000000000000000000000000011000 $ +#8 +0" +#9 +1" +b00000000000000000000000000000110 # +b00000000000000000000000001111000 $ diff --git a/test_regress/t/t_trace_lib_as_top_vcd.py b/test_regress/t/t_trace_lib_as_top_vcd.py new file mode 100755 index 000000000..b42540816 --- /dev/null +++ b/test_regress/t/t_trace_lib_as_top_vcd.py @@ -0,0 +1,15 @@ +#!/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 +import trace_lib_as_top_common + +test.scenarios('vlt_all') + +trace_lib_as_top_common.run(test) diff --git a/test_regress/t/trace_lib_as_top_common.py b/test_regress/t/trace_lib_as_top_common.py new file mode 100644 index 000000000..651e6cecd --- /dev/null +++ b/test_regress/t/trace_lib_as_top_common.py @@ -0,0 +1,56 @@ +# 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 os +import platform +import sys + + +def run(test, *, verilator_flags2=()): + fmt, = test.parse_name(r"t_trace_lib_as_top_([a-z]+)") + + if platform.system() == "Windows": + test.skip("Skipping on Windows: test depends on Unix-style shared-library loading") + + # All test use the same SV file + test.top_filename = "t/t_trace_lib_as_top.v" + test.pli_filename = os.path.abspath("t/t_trace_lib_as_top.cpp") + # Any variations after the format name must yield the exact same trace + test.golden_filename = test.py_filename.rpartition(fmt)[0] + fmt + ".out" + + flags = [ + f"--trace-{fmt}", + ] + flags.extend(verilator_flags2) + + cflags = (f'-DVM_PREFIX={test.vm_prefix} ' + f"-DVM_PREFIX_INCLUDE='<{test.vm_prefix}.h>' ") + + # Compile and run without lib-create + test.compile(verilator_flags2=[test.pli_filename] + flags + + ["--exe", "--build", "-CFLAGS", f'"{cflags}"']) + test.execute() + + test.trace_identical(test.trace_filename, test.golden_filename) + + # Compile and run with lib-create + test.compile(verilator_flags2=[test.pli_filename] + flags + + ["--build", "--lib-create", "simulator", "-CFLAGS", f'"{cflags}"']) + + # Load library and execute the simulation loop + # This is to avoid linking manually so that the test is portable + # Running in test.run() instead of directly to keep output in the test log + libsim = f"./{test.obj_dir}/libsimulator.so" + pycode = ("import ctypes;" + f"lib=ctypes.CDLL({libsim!r});" + "lib.sim_main(0, None)") + test.run(cmd=[sys.executable, "-c", f'"{pycode}"'], logfile=test.run_log_filename) + + test.trace_identical(test.trace_filename, test.golden_filename) + + test.passes()