Merge fdeccdc45e into 397e64903c
This commit is contained in:
commit
cbd79e5f16
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@ RAW_OBJS_PCH_ASTNOMT = \
|
|||
V3Tristate.o \
|
||||
V3Udp.o \
|
||||
V3Undriven.o \
|
||||
V3UndrivenCapture.o \
|
||||
V3Unknown.o \
|
||||
V3Unroll.o \
|
||||
V3UnrollGen.o \
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue