This commit is contained in:
Artur Bieniek 2026-04-03 11:16:15 +02:00 committed by GitHub
commit 507b0c2afa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 567 additions and 34 deletions

View File

@ -299,6 +299,7 @@ protected:
private:
std::vector<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp
std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing
std::vector<bool> m_initCbsCalled; // Init callbacks already run for this open
std::vector<CallbackRecord> m_constCbs; // Routines to perform const dump
std::vector<CallbackRecord> m_constOffloadCbs; // Routines to perform offloaded const dump
std::vector<CallbackRecord> 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<std::pair<int, std::string>> 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<T_Trace*>(this); }
void runInitCallback(size_t index, bool rootInit) VL_MT_UNSAFE;
void runCallbacks(const std::vector<CallbackRecord>& cbVec);
void runOffloadedCallbacks(const std::vector<CallbackRecord>& 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;

View File

@ -301,6 +301,25 @@ VerilatedTrace<VL_SUB_T, VL_BUF_T>::~VerilatedTrace() {
//=========================================================================
// Internals available to format-specific implementations
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
// Note: It is possible to re-open a trace file (VCD in particular),
@ -311,18 +330,13 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::addCleanupCb(cleanupCb_t cb, void* user
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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);
}
}

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -197,8 +197,38 @@ class TraceVisitor final : public VNVisitor {
// For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
class TraceInitDeclCollector final : public VNVisitor {
std::vector<AstTraceDecl*>& m_declps;
std::set<const AstCFunc*> 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<AstTraceDecl*>& 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<AstTraceDecl*> rootDeclps;
std::vector<AstTraceDecl*> 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<bool> 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) {

View File

@ -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<const AstTraceDecl*> 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<int>::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<AstNode*>(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<AstCFunc*>& 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 {

View File

@ -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 <verilated.h>
#include VM_PREFIX_INCLUDE
extern "C" int sim_main(int argc, char* argv[]) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
const std::unique_ptr<VM_PREFIX> 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); }

View File

@ -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

View File

@ -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 "

View File

@ -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)

View File

@ -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))
)
)
)
)
)

View File

@ -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)

View File

@ -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 $

View File

@ -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)

View File

@ -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()