From 471171701f37c22e15d89084d859daf37b0cbe98 Mon Sep 17 00:00:00 2001 From: em2machine Date: Sun, 21 Dec 2025 17:09:27 +0100 Subject: [PATCH] initial UndrivenCapture, just initial hacking --- src/CMakeLists.txt | 2 + src/Makefile_obj.in | 1 + src/V3UndrivenCapture.cpp | 201 ++++++++++++++++++++++++++++++++++++++ src/V3UndrivenCapture.h | 78 +++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 src/V3UndrivenCapture.cpp create mode 100644 src/V3UndrivenCapture.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a08cb45c2..705b2d0cf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -188,6 +188,7 @@ set(HEADERS V3Tristate.h V3Udp.h V3Undriven.h + V3UndrivenCapture.h V3UniqueNames.h V3Unknown.h V3Unroll.h @@ -356,6 +357,7 @@ set(COMMON_SOURCES V3Tristate.cpp V3Udp.cpp V3Undriven.cpp + V3UndrivenCapture.cpp V3Unknown.cpp V3Unroll.cpp V3UnrollGen.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 74696c1b1..adeaa97be 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -340,6 +340,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Tristate.o \ V3Udp.o \ V3Undriven.o \ + V3UndrivenCapture.o \ V3Unknown.o \ V3Unroll.o \ V3UnrollGen.o \ diff --git a/src/V3UndrivenCapture.cpp b/src/V3UndrivenCapture.cpp new file mode 100644 index 000000000..cf06278cb --- /dev/null +++ b/src/V3UndrivenCapture.cpp @@ -0,0 +1,201 @@ +// -*- 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" + +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +constexpr int DBG = 9; +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 ""; + 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); + } + + /* + V3UndrivenCapture::FTaskInfo& infoFor(const AstNodeFTask* taskp) { + // Ensure entry exists + return m_cap.writeSummary(taskp), const_cast( + *m_cap.find(taskp)); // not used; see below + } + */ + +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(DBG, "UndrivenCapture: enter ftask " << nodep << " " << nodep->prettyNameQ()); + (void)m_cap.ensureEntry(nodep); // ensure map entry exists + iterateListConst(*this, nodep->stmtsp()); + } + + void visit(AstNodeVarRef* nodep) override { + if (m_curTaskp && nodep->access().isWriteOrRW()) { + ++g_stats.varWrites; + UINFO(DBG, "UndrivenCapture: direct write in " << taskNameQ(m_curTaskp) << " var=" << nodep->varp()->prettyNameQ() << " at " << nodep->fileline()); + // Ensure entry exists + (void)m_cap.ensureEntry(m_curTaskp); + // Safe: find() must succeed after writeSummary() creates entry + auto* const infop = const_cast(m_cap.find(m_curTaskp)); + if (infop) infop->directWrites.push_back(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(DBG, "UndrivenCapture: call edge " << taskNameQ(m_curTaskp) << " -> " << taskNameQ(calleep)); + (void)m_cap.ensureEntry(m_curTaskp); + (void)m_cap.ensureEntry(calleep); + auto* const infop + = const_cast(m_cap.find(m_curTaskp)); + if (infop) infop->callees.push_back(calleep); + } else { + UINFO(DBG, "UndrivenCapture: unresolved call in " << taskNameQ(m_curTaskp) << " name=" << nodep->name()); + } + } + iterateChildrenConst(nodep); // still scan pins/args + } + + void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } +}; + +} // namespace + +// static +void V3UndrivenCapture::sortUniqueVars(std::vector& vec) { + std::sort(vec.begin(), vec.end()); + vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); +} + +// static +void V3UndrivenCapture::sortUniqueFTasks(std::vector& vec) { + std::sort(vec.begin(), vec.end()); + vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); +} + +V3UndrivenCapture::V3UndrivenCapture(AstNetlist* netlistp) { + gather(netlistp); + + // Normalize direct lists + for (auto& kv : m_info) { + sortUniqueVars(kv.second.directWrites); + sortUniqueFTasks(kv.second.callees); + } + + // Compute summaries for all tasks (MVP: DFS memoization, no SCC yet) + for (const auto& kv : m_info) (void)computeWriteSummary(kv.first); + + UINFO(DBG, "UndrivenCapture: 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(FTask taskp) const { + const auto it = m_info.find(taskp); + if (it == m_info.end()) return nullptr; + return &it->second; +} + +const std::vector& V3UndrivenCapture::writeSummary(FTask taskp) { + // Ensure entry exists even if empty + (void)m_info[taskp]; + return computeWriteSummary(taskp); +} + +const std::vector& V3UndrivenCapture::computeWriteSummary(FTask taskp) { + FTaskInfo& info = m_info[taskp]; + + if (info.state == State::DONE) { + UINFO(DBG, "UndrivenCapture: writeSummary cached size=" << info.writeSummary.size() << " for " << taskNameQ(taskp)); + return info.writeSummary; + } + if (info.state == State::VISITING) { + UINFO(DBG, "UndrivenCapture: recursion detected at " << taskNameQ(taskp) << " returning directWrites size=" << info.directWrites.size()); + // Cycle detected (recursion/mutual recursion). MVP behavior: + // return directWrites only to guarantee termination. + if (info.writeSummary.empty()) info.writeSummary = info.directWrites; + sortUniqueVars(info.writeSummary); + return info.writeSummary; + } + + info.state = State::VISITING; + + // Start with direct writes + info.writeSummary = info.directWrites; + + // Union in callees + for (FTask calleep : info.callees) { + if (m_info.find(calleep) == m_info.end()) continue; + const std::vector& sub = computeWriteSummary(calleep); + info.writeSummary.insert(info.writeSummary.end(), sub.begin(), sub.end()); + } + + sortUniqueVars(info.writeSummary); + + UINFO(DBG, "UndrivenCapture: writeSummary computed size=" << info.writeSummary.size() << " for " << taskNameQ(taskp)); + + info.state = State::DONE; + return info.writeSummary; +} + +void V3UndrivenCapture::debugDumpTask(FTask taskp, int level) const { + const auto* const infop = find(taskp); + if (!infop) { + UINFO(level, "UndrivenCapture: no entry for task " << taskp); + return; + } + UINFO(level, "UndrivenCapture: dump task " + << taskp << " " << taskp->prettyNameQ() + << " directWrites=" << infop->directWrites.size() + << " callees=" << infop->callees.size() + << " writeSummary=" << infop->writeSummary.size()); +} + +void V3UndrivenCapture::ensureEntry(FTask taskp) { (void)m_info[taskp]; } diff --git a/src/V3UndrivenCapture.h b/src/V3UndrivenCapture.h new file mode 100644 index 000000000..aaf5b253d --- /dev/null +++ b/src/V3UndrivenCapture.h @@ -0,0 +1,78 @@ +// -*- 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 +// +//************************************************************************* + +#ifndef VERILATOR_V3UNDRIVENCAPTURE_H_ +#define VERILATOR_V3UNDRIVENCAPTURE_H_ + +#include "config_build.h" + +#include "V3Ast.h" + +#include +#include + +class AstNetlist; + +class V3UndrivenCapture final { +public: + using FTask = const AstNodeFTask*; + using Var = AstVar*; + + // DFS computation state for writeSummary propagation. + enum class State : uint8_t { UNVISITED, VISITING, DONE }; + + struct FTaskInfo final { + // Variables written directly in this task/function body. + std::vector directWrites; + // Direct resolved callees from this task/function body. + std::vector callees; + // Transitive "may write" summary: directWrites plus all callees' summaries. + std::vector writeSummary; + // Memoization state for writeSummary computation. + State state = State::UNVISITED; + }; + +private: + // Per-task/function capture info keyed by resolved AstNodeFTask* identity. + std::unordered_map m_info; + + // Sort and remove duplicates from a vector of variables. + static void sortUniqueVars(std::vector& vec); + // Sort and remove duplicates from a vector of callees. + static void sortUniqueFTasks(std::vector& vec); + + // Collect direct writes and call edges for all tasks/functions. + void gather(AstNetlist* netlistp); + // Compute (and memoize) transitive writeSummary for the given task/function. + const std::vector& computeWriteSummary(FTask 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). + const FTaskInfo* find(FTask taskp) const; + // Get transitive writeSummary for a task/function (creates empty entry if needed). + const std::vector& writeSummary(FTask taskp); + + // Ensure an entry exists for a task/function without computing its writeSummary. + void ensureEntry(FTask taskp); + + // Optional: dump one task's summary (for debug bring-up). You can omit if you prefer. + void debugDumpTask(FTask taskp, int level = 9) const; +}; + +#endif // VERILATOR_V3UNDRIVENCAPTURE_H_ \ No newline at end of file