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"
2025-12-24 11:47:15 +01:00
//#include <algorithm>
2025-12-21 17:09:27 +01:00
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
2025-12-21 19:11:39 +01:00
bool V3UndrivenCapture : : enableWriteSummary = true ;
2025-12-21 18:22:42 +01:00
2025-12-21 17:09:27 +01:00
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
// 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 11:47:15 +01:00
//info.directWrites.push_back(varp);
// filter out duplicates.
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 11:47:15 +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.
2025-12-24 08:49:02 +01:00
if ( callerInfo . calleesSet . insert ( calleep ) . second ) {
callerInfo . callees . push_back ( calleep ) ;
}
2025-12-24 11:47:15 +01:00
// 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
}