This commit is contained in:
em2machine 2025-12-24 10:45:05 -05:00 committed by GitHub
commit cbd79e5f16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1212 additions and 15 deletions

View File

@ -189,6 +189,7 @@ set(HEADERS
V3Tristate.h
V3Udp.h
V3Undriven.h
V3UndrivenCapture.h
V3UniqueNames.h
V3Unknown.h
V3Unroll.h
@ -358,6 +359,7 @@ set(COMMON_SOURCES
V3Tristate.cpp
V3Udp.cpp
V3Undriven.cpp
V3UndrivenCapture.cpp
V3Unknown.cpp
V3Unroll.cpp
V3UnrollGen.cpp

View File

@ -341,6 +341,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Tristate.o \
V3Udp.o \
V3Undriven.o \
V3UndrivenCapture.o \
V3Unknown.o \
V3Unroll.o \
V3UnrollGen.o \

View File

@ -28,6 +28,7 @@
#include "V3Undriven.h"
#include "V3Stats.h"
#include "V3UndrivenCapture.h"
#include <vector>
@ -51,6 +52,9 @@ class UndrivenVarEntry final {
const FileLine* m_nodeFileLinep = nullptr; // File line of varref if driven, else nullptr
bool m_underGen = false; // Under a generate
const AstNodeFTaskRef* m_callNodep
= nullptr; // Call node if driven via writeSummary, else nullptr
enum : uint8_t { FLAG_USED = 0, FLAG_DRIVEN = 1, FLAG_DRIVEN_ALWCOMB = 2, FLAGS_PER_BIT = 3 };
public:
@ -278,6 +282,12 @@ public:
}
}
}
void drivenViaCall(const AstNodeFTaskRef* nodep) {
drivenWhole();
if (!m_callNodep) { m_callNodep = nodep; }
}
const AstNodeFTaskRef* callNodep() const { return m_callNodep; }
};
//######################################################################
@ -304,6 +314,8 @@ class UndrivenVisitor final : public VNVisitorConst {
const AstAlways* m_alwaysp = nullptr; // Current always of either type
const AstAlways* m_alwaysCombp = nullptr; // Current always if combo, otherwise nullptr
V3UndrivenCapture* const m_capturep = nullptr; // Capture object. 'nullptr' if disabled.
// METHODS
UndrivenVarEntry* getEntryp(AstVar* nodep, int which_user) {
@ -420,6 +432,16 @@ class UndrivenVisitor final : public VNVisitorConst {
<< " (IEEE 1800-2023 13.5): " << nodep->prettyNameQ());
}
}
// If writeSummary is enabled, task/function definitions are treated as non-executed.
// Their effects are applied at call sites via writeSummary(), so don't let definition
// traversal create phantom "other writes" for MULTIDRIVEN.
if (m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox
&& !m_taskp->dpiExport()) {
AstVar* const retVarp = VN_CAST(m_taskp->fvarp(), Var);
if (!retVarp || nodep->varp() != retVarp) return;
}
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
UndrivenVarEntry* const entryp = getEntryp(nodep->varp(), usr);
const bool fdrv = nodep->access().isWriteOrRW()
@ -432,7 +454,12 @@ class UndrivenVisitor final : public VNVisitorConst {
if (entryp->isDrivenWhole() && !m_inBBox && !VN_IS(nodep, VarXRef)
&& !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
&& nodep->fileline() != entryp->getNodeFileLinep() && !entryp->isUnderGen()
&& entryp->getNodep()) {
&& (entryp->getNodep() || entryp->callNodep())) {
const AstNode* const otherWritep
= entryp->getNodep() ? static_cast<const AstNode*>(entryp->getNodep())
: entryp->callNodep();
if (m_alwaysCombp
&& (!entryp->isDrivenAlwaysCombWhole()
|| (m_alwaysCombp != entryp->getAlwCombp()
@ -443,20 +470,18 @@ class UndrivenVisitor final : public VNVisitorConst {
<< " (IEEE 1800-2023 9.2.2.2): " << nodep->prettyNameQ() << '\n'
<< nodep->warnOther() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< entryp->getNodep()->warnOther()
<< "... Location of other write\n"
<< entryp->getNodep()->warnContextSecondary());
<< otherWritep->warnOther() << "... Location of other write\n"
<< otherWritep->warnContextSecondary());
}
if (!m_alwaysCombp && entryp->isDrivenAlwaysCombWhole()) {
nodep->v3warn(MULTIDRIVEN,
"Variable also written to in always_comb"
<< " (IEEE 1800-2023 9.2.2.2): " << nodep->prettyNameQ()
<< '\n'
<< nodep->warnOther() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< entryp->getNodep()->warnOther()
<< "... Location of always_comb write\n"
<< entryp->getNodep()->warnContextSecondary());
nodep->v3warn(MULTIDRIVEN, "Variable also written to in always_comb"
<< " (IEEE 1800-2023 9.2.2.2): "
<< nodep->prettyNameQ() << '\n'
<< nodep->warnOther() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< otherWritep->warnOther()
<< "... Location of always_comb write\n"
<< otherWritep->warnContextSecondary());
}
}
entryp->drivenWhole(nodep, nodep->fileline());
@ -523,10 +548,39 @@ class UndrivenVisitor final : public VNVisitorConst {
iterateChildrenConst(nodep);
if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9, " Done " << nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
VL_RESTORER(m_inFTaskRef);
m_inFTaskRef = true;
iterateChildrenConst(nodep);
if (!m_capturep) return;
// If writeSummary is enabled, task/function definitions are treated as non-executed.
// Do not apply writeSummary at calls inside a task definition, or they will look like
// independent drivers (phantom MULTIDRIVEN). Did the lambda on purpose - lessen chance of
// screwup in future edits.
const auto inExecutedContext = [this]() {
return !(m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox
&& !m_taskp->dpiExport());
};
if (!inExecutedContext()) return;
AstNodeFTask* const calleep = nodep->taskp();
if (!calleep) return;
const auto& vars = m_capturep->writeSummary(calleep);
for (AstVar* const varp : vars) {
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
UndrivenVarEntry* const entryp = getEntryp(varp, usr);
entryp->drivenViaCall(nodep);
if (m_alwaysCombp)
entryp->drivenAlwaysCombWhole(m_alwaysCombp, m_alwaysCombp->fileline());
}
}
}
void visit(AstNodeFTask* nodep) override {
@ -556,7 +610,11 @@ class UndrivenVisitor final : public VNVisitorConst {
public:
// CONSTRUCTORS
explicit UndrivenVisitor(AstNetlist* nodep) { iterateConst(nodep); }
explicit UndrivenVisitor(AstNetlist* nodep, V3UndrivenCapture* capturep)
: m_capturep{capturep} {
iterateConst(nodep);
}
~UndrivenVisitor() override {
for (UndrivenVarEntry* ip : m_entryps[1]) ip->reportViolations();
for (int usr = 1; usr < 3; ++usr) {
@ -570,6 +628,9 @@ public:
void V3Undriven::undrivenAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ UndrivenVisitor{nodep}; }
V3UndrivenCapture capture{nodep};
UndrivenVisitor{nodep, &capture};
if (v3Global.opt.stats()) V3Stats::statsStage("undriven");
}

212
src/V3UndrivenCapture.cpp Normal file
View File

@ -0,0 +1,212 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Capture task/function write summaries for undriven checks
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3UndrivenCapture.h"
#include "V3Error.h"
#include "V3Global.h"
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
struct Stats final {
uint64_t ftasks = 0;
uint64_t varWrites = 0;
uint64_t callEdges = 0;
} g_stats;
static std::string taskNameQ(const AstNodeFTask* taskp) {
if (!taskp) return "<null>";
return taskp->prettyNameQ();
}
class CaptureVisitor final : public VNVisitorConst {
V3UndrivenCapture& m_cap;
const AstNodeFTask* m_curTaskp = nullptr;
static void iterateListConst(VNVisitorConst& v, AstNode* nodep) {
for (AstNode* np = nodep; np; np = np->nextp()) np->accept(v);
}
public:
explicit CaptureVisitor(V3UndrivenCapture& cap, AstNetlist* netlistp)
: m_cap{cap} {
iterateConst(netlistp);
}
private:
// Visit a task/function definition and collect direct writes and direct callees.
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_curTaskp);
m_curTaskp = nodep;
++g_stats.ftasks;
UINFO(9, "undriven capture enter ftask " << nodep << " " << nodep->prettyNameQ());
m_cap.noteTask(nodep);
iterateListConst(*this, nodep->stmtsp());
}
void visit(AstNodeVarRef* nodep) override {
if (m_curTaskp && nodep->access().isWriteOrRW()) {
++g_stats.varWrites;
UINFO(9, "undriven capture direct write in " << taskNameQ(m_curTaskp)
<< " var=" << nodep->varp()->prettyNameQ()
<< " at " << nodep->fileline());
m_cap.noteDirectWrite(m_curTaskp, nodep->varp());
}
iterateChildrenConst(nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
// Record the call edge if resolved
if (m_curTaskp) {
if (AstNodeFTask* const calleep = nodep->taskp()) {
++g_stats.callEdges;
UINFO(9, "undriven capture call edge " << taskNameQ(m_curTaskp) << " -> "
<< taskNameQ(calleep));
m_cap.noteCallEdge(m_curTaskp, calleep);
} else {
UINFO(9, "undriven capture unresolved call in " << taskNameQ(m_curTaskp)
<< " name=" << nodep->name());
}
}
iterateChildrenConst(nodep); // still scan pins/args
}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
};
} // namespace
V3UndrivenCapture::V3UndrivenCapture(AstNetlist* netlistp) {
gather(netlistp);
// Compute summaries for all tasks
for (const auto& kv : m_info) (void)computeWriteSummary(kv.first);
// Release the filter memory
for (auto& kv : m_info) {
kv.second.calleesSet.clear();
kv.second.calleesSet.rehash(0);
kv.second.directWritesSet.clear();
kv.second.directWritesSet.rehash(0);
}
UINFO(9, "undriven capture stats ftasks="
<< g_stats.ftasks << " varWrites=" << g_stats.varWrites
<< " callEdges=" << g_stats.callEdges << " uniqueTasks=" << m_info.size());
}
void V3UndrivenCapture::gather(AstNetlist* netlistp) {
// Walk netlist and populate m_info with direct writes + call edges
CaptureVisitor{*this, netlistp};
}
const V3UndrivenCapture::FTaskInfo* V3UndrivenCapture::find(const AstNodeFTask* taskp) const {
const auto it = m_info.find(taskp);
if (it == m_info.end()) return nullptr;
return &it->second;
}
const std::vector<AstVar*>& V3UndrivenCapture::writeSummary(const AstNodeFTask* taskp) {
// Ensure entry exists even if empty
(void)m_info[taskp];
return computeWriteSummary(taskp);
}
const std::vector<AstVar*>& V3UndrivenCapture::computeWriteSummary(const AstNodeFTask* taskp) {
FTaskInfo& info = m_info[taskp];
if (info.state == State::DONE) {
UINFO(9, "undriven capture writeSummary cached size=" << info.writeSummary.size()
<< " for " << taskNameQ(taskp));
return info.writeSummary;
}
if (info.state == State::VISITING) {
UINFO(9, "undriven capture recursion detected at "
<< taskNameQ(taskp)
<< " returning directWrites size=" << info.directWrites.size());
// Cycle detected. return directWrites only to guarantee termination.
if (info.writeSummary.empty()) info.writeSummary = info.directWrites;
return info.writeSummary;
}
info.state = State::VISITING;
info.writeSummary.clear();
// Prevent duplicates across all sources that can contribute to a write summary (direct writes
// and call chains)
std::unordered_set<AstVar*> seen;
// Simple lambda for filtering duplicates
auto addVar = [&](AstVar* v) {
if (seen.insert(v).second) info.writeSummary.push_back(v);
};
// Start with direct writes
for (AstVar* v : info.directWrites) addVar(v);
// Add callee summaries
for (const AstNodeFTask* calleep : info.callees) {
if (m_info.find(calleep) == m_info.end()) continue;
const std::vector<AstVar*>& sub = computeWriteSummary(calleep);
for (AstVar* v : sub) addVar(v);
}
UINFO(9, "undriven capture writeSummary computed size=" << info.writeSummary.size() << " for "
<< taskNameQ(taskp));
// We are done, so set the m_info state correctly and return the vector of variables
info.state = State::DONE;
return info.writeSummary;
}
void V3UndrivenCapture::noteTask(const AstNodeFTask* taskp) { (void)m_info[taskp]; }
void V3UndrivenCapture::noteDirectWrite(const AstNodeFTask* taskp, AstVar* varp) {
FTaskInfo& info = m_info[taskp];
// Exclude function return variable (not an externally visible side-effect)
AstVar* const retVarp = VN_CAST(taskp->fvarp(), Var);
if (retVarp && varp == retVarp) return;
// Filter out duplicates.
if (info.directWritesSet.insert(varp).second) { info.directWrites.push_back(varp); }
}
void V3UndrivenCapture::noteCallEdge(const AstNodeFTask* callerp, const AstNodeFTask* calleep) {
FTaskInfo& callerInfo = m_info[callerp];
// Prevents duplicate entries from being appended, if calleep already exists then insert will
// return false, and then is not inserted into the callees vector.
if (callerInfo.calleesSet.insert(calleep).second) { callerInfo.callees.push_back(calleep); }
// Ensure callee entry exists, if already exists then this is a no-op. unordered_map<> so
// cheap.
(void)m_info[calleep];
}
void V3UndrivenCapture::debugDumpTask(const AstNodeFTask* taskp, int level) const {
const auto* const infop = find(taskp);
if (!infop) {
UINFO(level, "undriven capture no entry for task " << taskp);
return;
}
UINFO(level, "undriven capture dump task " << taskp << " " << taskp->prettyNameQ()
<< " directWrites=" << infop->directWrites.size()
<< " callees=" << infop->callees.size()
<< " writeSummary=" << infop->writeSummary.size());
}

108
src/V3UndrivenCapture.h Normal file
View File

@ -0,0 +1,108 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Capture task/function write summaries for undriven checks
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//*************************************************************************
//
// Capture task/function write summaries for multidriven checks.
// Per-task/function capture info keyed by resolved AstNodeFTask* identity (FTask = function or
// task). This is our 'graph' of tasks/functions. Each node has a list of direct callees and
// a list of variables written in the function body. There are methods to dedup after walking the
// tree. V3Undriven then uses the writeSummary for multidriven checks - i.e. it treats writes (side
// effects) inside subroutines as part of the caller's process.
//
//*************************************************************************
#ifndef VERILATOR_V3UNDRIVENCAPTURE_H_
#define VERILATOR_V3UNDRIVENCAPTURE_H_
#include "config_build.h"
#include "V3Ast.h"
#include <unordered_map>
#include <unordered_set>
#include <vector>
class AstNetlist;
class V3UndrivenCapture final {
public:
// DFS computation state for writeSummary propagation.
// UNVISITED: write summary not computed yet
// VISITING: currently computing on the call stack - used to detect cycles
// DONE: write summary computed
enum class State : uint8_t { UNVISITED, VISITING, DONE };
struct FTaskInfo final {
// Variables written directly in this task/function body (iteration order)
std::vector<AstVar*> directWrites;
// Direct resolved callees from this task/function body (iteration order)
std::vector<const AstNodeFTask*> callees;
// 'Write through write' writeSummary for the given task/function. Meaning ultimately
// everything that this function/task writes to.
std::vector<AstVar*> writeSummary;
// State for writeSummary computation.
State state = State::UNVISITED;
// This is used to test whether weve already recorded a callee. Used to 'filter' on insert
// versus sorting at the end.
std::unordered_set<const AstNodeFTask*> calleesSet;
// This is used to test whether weve already recorded a direct write. Used to 'filter' on
// insert versus sorting at the end.
std::unordered_set<AstVar*> directWritesSet;
};
private:
// Per-task/function capture info keyed by resolved AstNodeFTask* identity (FTask = function or
// task). This is our 'graph' of tasks/functions. Each node has a list of direct callees and
// a list of variables written in the function body. There are methods to remove duplicates
// otherwise this could explode.
std::unordered_map<const AstNodeFTask*, FTaskInfo> m_info;
// Collect direct writes and call edges for all tasks/functions. Run one time when
// UndrivenCapture is created. This runs the visitor over the tree.
void gather(AstNetlist* netlistp);
// Compute (and cache) 'write through write' writeSummary for the given task/function.
const std::vector<AstVar*>& computeWriteSummary(const AstNodeFTask* taskp);
public:
// Build capture database and precompute writeSummary for all discovered tasks/functions.
explicit V3UndrivenCapture(AstNetlist* netlistp);
// Lookup task/function capture info (nullptr if unknown). This is currently only used for the
// debug helper.
const FTaskInfo* find(const AstNodeFTask* taskp) const;
// Get write through write through write, etc (call chain) writeSummary for a task/function
// (creates empty entry if needed). This returns a vector of variables that a particular
// task/function writes to, including all variables written by functions called by this
// task/function, and so on.
const std::vector<AstVar*>& writeSummary(const AstNodeFTask* taskp);
// Used by the capture visitor to record information about tasks/functions and their statements
// and callees. noteTask() makes sure there is an entry for the given taskp.
void noteTask(const AstNodeFTask* taskp);
// Inside the body of taskp there is a write to variable varp
void noteDirectWrite(const AstNodeFTask* taskp, AstVar* varp);
// Inside the body of callerp there is a call to calleep, this is needed so we can create a
// summary that includes all variables written by functions called by this task/function, and
// so on.
void noteCallEdge(const AstNodeFTask* callerp, const AstNodeFTask* calleep);
// Dump one task's summary for debugging. leaving this in, in case need to debug future
// functionality.
void debugDumpTask(const AstNodeFTask* taskp, int level = 9) const;
};
#endif // VERILATOR_V3UNDRIVENCAPTURE_H_

View File

@ -0,0 +1,11 @@
%Warning-MULTIDRIVEN: t/t_lint_multidriven_taskcall_bad.v:28:15: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out'
: ... note: In instance 't'
t/t_lint_multidriven_taskcall_bad.v:28:15:
28 | if (sel2) out = 1'b1;
| ^~~
t/t_lint_multidriven_taskcall_bad.v:20:5: ... Location of other write
20 | out = 1'b0;
| ^~~
... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest
... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty.
// SPDX-License-Identifier: CC0-1.0
module t (
input logic sel,
input logic sel2,
input logic d,
output logic out
);
task automatic do_stuff(input logic din);
out = din;
endtask
// Driver #1 (via task call)
always_comb begin
out = 1'b0;
if (sel) do_stuff(d);
end
// Driver #2 (separate process)
// I only want the MULTIDRIVEN.
/* verilator lint_off LATCH */
always_comb begin
if (sel2) out = 1'b1;
end
/* verilator lint_on LATCH */
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,284 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty.
// SPDX-License-Identifier: CC0-1.0
// Consolidated class-based task/function multidriven tests
// (formerly t_multidriven_class{0,1,2,3,4,f0,f1}.v)
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
//----------------------------------------------------------------------
// class0: class task writes through ref argument (direct assignment + class task in same always_comb)
class C0;
task automatic set1(ref logic q);
q = 1'b1;
endtask
task automatic set0(ref logic q);
q = 1'b0;
endtask
endclass
module class0 #()(
input logic sel
,output logic val
);
logic l0;
C0 c;
initial c = new;
always_comb begin
l0 = 1'b0;
if (sel) begin
c.set1(l0);
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// class1: class task chain - nested method calls write through ref in same always_comb
class C1;
task automatic inner(inout logic q);
q = 1'b1;
endtask
task automatic outer(inout logic q);
inner(q);
endtask
endclass
module class1 #()(
input logic sel
,output logic val
);
logic l0;
C1 c;
initial c = new;
always_comb begin
l0 = 1'b0;
if (sel) begin
c.outer(l0);
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// class2: class handle passed through module port - class method writes through ref
class C2;
task automatic set1(ref logic q);
q = 1'b1;
endtask
endclass
module class2 #()(
input logic sel
,output logic val
,C2 c
);
logic l0;
always_comb begin
l0 = 1'b0;
if (sel) begin
c.set1(l0);
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// class3: static class task - call via class scope, writes through ref in same always_comb
class C3;
static task automatic set1(ref logic q);
q = 1'b1;
endtask
endclass
module class3 #()(
input logic sel
,output logic val
);
logic l0;
always_comb begin
l0 = 1'b0;
if (sel) begin
C3::set1(l0);
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// class4: class composition - one class calls another task, ultimately writes through ref
class C4Inner;
task automatic set1(ref logic q);
q = 1'b1;
endtask
endclass
class C4Outer;
C4Inner inner;
function new();
inner = new;
endfunction
task automatic set1(ref logic q);
inner.set1(q);
endtask
endclass
module class4 #()(
input logic sel
,output logic val
);
logic l0;
C4Outer c;
initial c = new;
always_comb begin
l0 = 1'b0;
if (sel) begin
c.set1(l0);
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// classf0: class function returns value - always_comb writes var directly + via class function call
class Cf0;
function automatic logic ret1();
return 1'b1;
endfunction
endclass
module classf0 #()(
input logic sel
,output logic val
);
logic l0;
Cf0 c;
initial c = new;
always_comb begin
l0 = 1'b0;
if (sel) begin
l0 = c.ret1();
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// classf1: static class function returns value - always_comb uses class scope call
class Cf1;
static function automatic logic ret1();
return 1'b1;
endfunction
endclass
module classf1 #()(
input logic sel
,output logic val
);
logic l0;
always_comb begin
l0 = 1'b0;
if (sel) begin
l0 = Cf1::ret1();
end
end
assign val = l0;
endmodule
//----------------------------------------------------------------------
// Shared TB
module m_tb#()();
logic sel;
logic val0, val1, val2, val3, val4, valf0, valf1;
C2 c2;
initial c2 = new;
class0 u0(.sel(sel), .val(val0));
class1 u1(.sel(sel), .val(val1));
class2 u2(.sel(sel), .val(val2), .c(c2));
class3 u3(.sel(sel), .val(val3));
class4 u4(.sel(sel), .val(val4));
classf0 uf0(.sel(sel), .val(valf0));
classf1 uf1(.sel(sel), .val(valf1));
task automatic check_all(input logic exp);
`checkd(val0, exp);
`checkd(val1, exp);
`checkd(val2, exp);
`checkd(val3, exp);
`checkd(val4, exp);
`checkd(valf0, exp);
`checkd(valf1, exp);
endtask
initial begin
#1;
sel = 'b0;
#1;
check_all(1'b0);
sel = 'b1;
#1;
check_all(1'b1);
sel = 'b0;
#1;
check_all(1'b0);
end
initial begin
#5;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,43 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty.
// SPDX-License-Identifier: CC0-1.0
// MULTIDRIVEN false positive - package function return var
//
// Minimal reproducer for: package function with "return expr" used in always_comb expression.
// The function return variable must not be treated as a side-effect "writeSummary" target.
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
package p;
function automatic int num_bytes(input int size);
return 1 << size;
endfunction
endpackage
module t;
typedef struct packed {
logic [31:0] addr;
logic [2:0] size;
} meta_t;
meta_t rd_meta_q;
meta_t rd_meta;
always_comb begin
rd_meta = rd_meta_q;
rd_meta.addr = rd_meta_q.addr + p::num_bytes(int'(rd_meta_q.size));
end
initial begin
rd_meta_q.addr = 32'h100;
rd_meta_q.size = 3'd2; // num_bytes = 4
#1;
`checkd(rd_meta.addr, 32'h104);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,235 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty.
// SPDX-License-Identifier: CC0-1.0
// Consolidated interface-based multidriven tests
// (formerly t_multidriven_iface{0,1,2,3,4,5,6}.v)
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
//----------------------------------------------------------------------
// iface0: direct assignment to interface signal + interface task assign in same process
interface my_if0;
logic l0;
task set_l0_1(); l0 = 1'b1; endtask
task set_l0_0(); l0 = 1'b0; endtask
endinterface
module iface0 #()(
input logic sel,
output logic val
);
my_if0 if0();
always_comb begin
if0.l0 = 1'b0;
if (sel) begin
if0.set_l0_1();
end
end
assign val = if0.l0;
endmodule
//----------------------------------------------------------------------
// iface1: interface task chain - nested calls write interface signal in same always_comb
interface my_if1;
logic l0;
task set_l0_1_inner(); l0 = 1'b1; endtask
task set_l0_1_outer(); set_l0_1_inner(); endtask
endinterface
module iface1 #()(
input logic sel,
output logic val
);
my_if1 if0();
always_comb begin
if0.l0 = 1'b0;
if (sel) begin
if0.set_l0_1_outer();
end
end
assign val = if0.l0;
endmodule
//----------------------------------------------------------------------
// iface2: interface passed through module port - direct assign + task call in same always_comb
interface my_if2;
logic l0;
task set_l0_1(); l0 = 1'b1; endtask
task set_l0_0(); l0 = 1'b0; endtask
endinterface
module iface2 #()(
input logic sel,
output logic val,
my_if2 ifp
);
always_comb begin
ifp.l0 = 1'b0;
if (sel) begin
ifp.set_l0_1();
end
end
assign val = ifp.l0;
endmodule
//----------------------------------------------------------------------
// iface3: interface modport + task import - write interface signal in same always_comb
interface my_if3;
logic l0;
task set_l0_1(); l0 = 1'b1; endtask
modport mp (
output l0,
import set_l0_1
);
endinterface
module iface3 #()(
input logic sel,
output logic val,
my_if3.mp ifp
);
always_comb begin
ifp.l0 = 1'b0;
if (sel) begin
ifp.set_l0_1();
end
end
assign val = ifp.l0;
endmodule
//----------------------------------------------------------------------
// iface4: interface task writes through output formal - actual is interface member
interface my_if4;
logic l0;
task automatic set_any(output logic q);
q = 1'b1;
endtask
endinterface
module iface4 #()(
input logic sel,
output logic val
);
my_if4 if0();
always_comb begin
if0.l0 = 1'b0;
if (sel) begin
if0.set_any(if0.l0);
end
end
assign val = if0.l0;
endmodule
//----------------------------------------------------------------------
// iface5: nested interface test - direct assignment + nested interface task call in same always_comb
interface leaf_if5;
logic l0;
task set1(); l0 = 1'b1; endtask
endinterface
interface top_if5;
leaf_if5 sub();
endinterface
module iface5 #()(
input logic sel,
output logic val
);
top_if5 if0();
always_comb begin
if0.sub.l0 = 1'b0;
if (sel) begin
if0.sub.set1();
end
end
assign val = if0.sub.l0;
endmodule
//----------------------------------------------------------------------
// iface6: nested interface aggregator - two nested interfaces, only one driven
interface chan_if6;
logic b0;
task set1(); b0 = 1'b1; endtask
endinterface
interface agg_if6;
chan_if6 tlb();
chan_if6 ic();
endinterface
module iface6 #()(
input logic sel,
output logic val
);
agg_if6 a();
always_comb begin
a.tlb.b0 = 1'b0;
if (sel) a.tlb.set1();
end
assign val = a.tlb.b0;
endmodule
//----------------------------------------------------------------------
// Shared TB
module m_tb#()();
logic sel;
logic val0, val1, val2, val3, val4, val5, val6;
my_if2 if2();
my_if3 if3();
iface0 u0(.sel(sel), .val(val0));
iface1 u1(.sel(sel), .val(val1));
iface2 u2(.sel(sel), .val(val2), .ifp(if2));
iface3 u3(.sel(sel), .val(val3), .ifp(if3));
iface4 u4(.sel(sel), .val(val4));
iface5 u5(.sel(sel), .val(val5));
iface6 u6(.sel(sel), .val(val6));
task automatic check_all(input logic exp);
`checkd(val0, exp);
`checkd(val1, exp);
`checkd(val2, exp);
`checkd(val3, exp);
`checkd(val4, exp);
`checkd(val5, exp);
`checkd(val6, exp);
endtask
initial begin
#1;
sel = 'b0;
#1;
check_all(1'b0);
sel = 'b1;
#1;
check_all(1'b1);
sel = 'b0;
#1;
check_all(1'b0);
end
initial begin
#5;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,120 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty.
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
// direct task call
module mod0 #()(
input logic sel,
output logic val
);
logic l0;
task do_stuff();
l0 = 'b1;
endtask
always_comb begin
l0 = 'b0;
if(sel) begin
do_stuff();
end
end
assign val = l0;
endmodule
// nested task call chain
module mod1 #()(
input logic sel,
output logic val
);
logic l0;
task do_inner();
l0 = 'b1;
endtask
task do_outer();
do_inner();
endtask
always_comb begin
l0 = 'b0;
if (sel) do_outer();
end
assign val = l0;
endmodule
// task writes through an output arguement
module mod2 #()(
input logic sel,
output logic val
);
logic l0;
task automatic do_stuff(output logic q);
q = 1'b1;
endtask
always_comb begin
l0 = 1'b0;
if (sel) do_stuff(l0);
end
assign val = l0;
endmodule
// function call that writes
module mod3 #()(
input logic sel,
output logic val
);
logic l0;
function automatic void do_func();
l0 = 1'b1;
endfunction
always_comb begin
l0 = 1'b0;
if (sel) do_func();
end
assign val = l0;
endmodule
// two tasks set0/set1
module mod4 #()(
input logic sel,
output logic val
);
logic l0;
task automatic set1(); l0 = 1'b1; endtask
task automatic set0(); l0 = 1'b0; endtask
always_comb begin
set0();
if (sel) begin
set1();
end
end
assign val = l0;
endmodule
module m_tb;
logic sel;
logic v0, v1, v2, v3, v4;
mod0 u0(.sel(sel), .val(v0));
mod1 u1(.sel(sel), .val(v1));
mod2 u2(.sel(sel), .val(v2));
mod3 u3(.sel(sel), .val(v3));
mod4 u4(.sel(sel), .val(v4));
initial begin
#1; sel = 0;
`checkd(v0, 0); `checkd(v1, 0); `checkd(v2, 0); `checkd(v3, 0); `checkd(v4, 0);
#1; sel = 1;
`checkd(v0, 1); `checkd(v1, 1); `checkd(v2, 1); `checkd(v3, 1); `checkd(v4, 1);
#1; sel = 0;
`checkd(v0, 0); `checkd(v1, 0); `checkd(v2, 0); `checkd(v3, 0); `checkd(v4, 0);
#1;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule