2025-12-21 17:09:27 +01:00
|
|
|
// -*- 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;
|
2025-12-21 19:11:39 +01:00
|
|
|
|
2025-12-21 17:09:27 +01:00
|
|
|
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;
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture enter ftask " << nodep << " " << nodep->prettyNameQ());
|
2025-12-22 12:25:27 +01:00
|
|
|
m_cap.noteTask(nodep);
|
2025-12-21 17:09:27 +01:00
|
|
|
iterateListConst(*this, nodep->stmtsp());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(AstNodeVarRef* nodep) override {
|
|
|
|
|
if (m_curTaskp && nodep->access().isWriteOrRW()) {
|
|
|
|
|
++g_stats.varWrites;
|
2025-12-22 18:42:33 +01:00
|
|
|
UINFO(9, "undriven capture direct write in " << taskNameQ(m_curTaskp)
|
|
|
|
|
<< " var=" << nodep->varp()->prettyNameQ()
|
|
|
|
|
<< " at " << nodep->fileline());
|
2025-12-22 12:25:27 +01:00
|
|
|
|
|
|
|
|
m_cap.noteDirectWrite(m_curTaskp, nodep->varp());
|
2025-12-21 17:09:27 +01:00
|
|
|
}
|
|
|
|
|
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;
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture call edge " << taskNameQ(m_curTaskp) << " -> "
|
2025-12-22 18:42:33 +01:00
|
|
|
<< taskNameQ(calleep));
|
2025-12-22 12:25:27 +01:00
|
|
|
m_cap.noteCallEdge(m_curTaskp, calleep);
|
2025-12-21 17:09:27 +01:00
|
|
|
} else {
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture unresolved call in " << taskNameQ(m_curTaskp)
|
2025-12-22 18:42:33 +01:00
|
|
|
<< " name=" << nodep->name());
|
2025-12-21 17:09:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
iterateChildrenConst(nodep); // still scan pins/args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
V3UndrivenCapture::V3UndrivenCapture(AstNetlist* netlistp) {
|
|
|
|
|
gather(netlistp);
|
|
|
|
|
|
2025-12-21 21:43:45 +01:00
|
|
|
// Compute summaries for all tasks
|
2025-12-21 17:09:27 +01:00
|
|
|
for (const auto& kv : m_info) (void)computeWriteSummary(kv.first);
|
|
|
|
|
|
2025-12-24 11:47:15 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture stats ftasks="
|
2025-12-22 18:42:33 +01:00
|
|
|
<< g_stats.ftasks << " varWrites=" << g_stats.varWrites
|
|
|
|
|
<< " callEdges=" << g_stats.callEdges << " uniqueTasks=" << m_info.size());
|
2025-12-21 17:09:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3UndrivenCapture::gather(AstNetlist* netlistp) {
|
|
|
|
|
// Walk netlist and populate m_info with direct writes + call edges
|
|
|
|
|
CaptureVisitor{*this, netlistp};
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
const V3UndrivenCapture::FTaskInfo* V3UndrivenCapture::find(const AstNodeFTask* taskp) const {
|
2025-12-21 17:09:27 +01:00
|
|
|
const auto it = m_info.find(taskp);
|
|
|
|
|
if (it == m_info.end()) return nullptr;
|
|
|
|
|
return &it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
const std::vector<AstVar*>& V3UndrivenCapture::writeSummary(const AstNodeFTask* taskp) {
|
2025-12-21 17:09:27 +01:00
|
|
|
// Ensure entry exists even if empty
|
|
|
|
|
(void)m_info[taskp];
|
|
|
|
|
return computeWriteSummary(taskp);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
const std::vector<AstVar*>& V3UndrivenCapture::computeWriteSummary(const AstNodeFTask* taskp) {
|
2025-12-21 17:09:27 +01:00
|
|
|
FTaskInfo& info = m_info[taskp];
|
|
|
|
|
|
|
|
|
|
if (info.state == State::DONE) {
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture writeSummary cached size=" << info.writeSummary.size()
|
2025-12-22 18:42:33 +01:00
|
|
|
<< " for " << taskNameQ(taskp));
|
2025-12-21 17:09:27 +01:00
|
|
|
return info.writeSummary;
|
|
|
|
|
}
|
|
|
|
|
if (info.state == State::VISITING) {
|
2025-12-22 16:01:08 +01:00
|
|
|
UINFO(9, "undriven capture recursion detected at "
|
2025-12-22 18:42:33 +01:00
|
|
|
<< taskNameQ(taskp)
|
|
|
|
|
<< " returning directWrites size=" << info.directWrites.size());
|
2025-12-22 13:10:42 +01:00
|
|
|
// Cycle detected. return directWrites only to guarantee termination.
|
2025-12-21 17:09:27 +01:00
|
|
|
if (info.writeSummary.empty()) info.writeSummary = info.directWrites;
|
|
|
|
|
return info.writeSummary;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info.state = State::VISITING;
|
|
|
|
|
|
2025-12-24 11:47:15 +01:00
|
|
|
info.writeSummary.clear();
|
2025-12-24 11:57:20 +01:00
|
|
|
|
2025-12-24 14:08:12 +01:00
|
|
|
// Prevent duplicates across all sources that can contribute to a write summary (direct writes
|
|
|
|
|
// and call chains)
|
2025-12-24 11:47:15 +01:00
|
|
|
std::unordered_set<AstVar*> seen;
|
|
|
|
|
|
2025-12-24 11:57:20 +01:00
|
|
|
// Simple lambda for filtering duplicates
|
2025-12-24 11:47:15 +01:00
|
|
|
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
|
2025-12-22 17:58:11 +01:00
|
|
|
for (const AstNodeFTask* calleep : info.callees) {
|
2025-12-21 17:09:27 +01:00
|
|
|
if (m_info.find(calleep) == m_info.end()) continue;
|
2025-12-22 17:58:11 +01:00
|
|
|
const std::vector<AstVar*>& sub = computeWriteSummary(calleep);
|
2025-12-24 11:47:15 +01:00
|
|
|
for (AstVar* v : sub) addVar(v);
|
2025-12-21 17:09:27 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 18:42:33 +01:00
|
|
|
UINFO(9, "undriven capture writeSummary computed size=" << info.writeSummary.size() << " for "
|
|
|
|
|
<< taskNameQ(taskp));
|
2025-12-21 17:09:27 +01:00
|
|
|
|
2025-12-22 13:10:42 +01:00
|
|
|
// We are done, so set the m_info state correctly and return the vector of variables
|
2025-12-21 17:09:27 +01:00
|
|
|
info.state = State::DONE;
|
|
|
|
|
return info.writeSummary;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
void V3UndrivenCapture::noteTask(const AstNodeFTask* taskp) { (void)m_info[taskp]; }
|
2025-12-22 12:25:27 +01:00
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
void V3UndrivenCapture::noteDirectWrite(const AstNodeFTask* taskp, AstVar* varp) {
|
2025-12-22 12:25:27 +01:00
|
|
|
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;
|
|
|
|
|
|
2025-12-24 13:48:15 +01:00
|
|
|
// Filter out duplicates.
|
2025-12-24 14:08:12 +01:00
|
|
|
if (info.directWritesSet.insert(varp).second) { info.directWrites.push_back(varp); }
|
2025-12-22 12:25:27 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
void V3UndrivenCapture::noteCallEdge(const AstNodeFTask* callerp, const AstNodeFTask* calleep) {
|
2025-12-24 08:49:02 +01:00
|
|
|
FTaskInfo& callerInfo = m_info[callerp];
|
2025-12-24 14:08:12 +01:00
|
|
|
// 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.
|
2025-12-24 08:49:02 +01:00
|
|
|
(void)m_info[calleep];
|
2025-12-22 12:25:27 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 17:58:11 +01:00
|
|
|
void V3UndrivenCapture::debugDumpTask(const AstNodeFTask* taskp, int level) const {
|
2025-12-21 17:09:27 +01:00
|
|
|
const auto* const infop = find(taskp);
|
|
|
|
|
if (!infop) {
|
2025-12-22 12:25:27 +01:00
|
|
|
UINFO(level, "undriven capture no entry for task " << taskp);
|
2025-12-21 17:09:27 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2025-12-22 12:25:27 +01:00
|
|
|
UINFO(level, "undriven capture dump task " << taskp << " " << taskp->prettyNameQ()
|
2025-12-21 21:48:16 +01:00
|
|
|
<< " directWrites=" << infop->directWrites.size()
|
|
|
|
|
<< " callees=" << infop->callees.size()
|
|
|
|
|
<< " writeSummary=" << infop->writeSummary.size());
|
2025-12-21 17:09:27 +01:00
|
|
|
}
|