diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9add1edaa..59eac5cf8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,6 +115,7 @@ set(HEADERS V3Inline.h V3Inst.h V3InstrCount.h + V3Instrumentation.h V3Interface.h V3LangCode.h V3LanguageWords.h @@ -285,6 +286,7 @@ set(COMMON_SOURCES V3Inline.cpp V3Inst.cpp V3InstrCount.cpp + V3Instrumentation.cpp V3Interface.cpp V3Life.cpp V3LifePost.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 0abed384c..c52ec7b21 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -285,6 +285,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Inline.o \ V3Inst.o \ V3InstrCount.o \ + V3Instrumentation.o \ V3Interface.o \ V3Life.o \ V3LifePost.o \ diff --git a/src/V3Control.cpp b/src/V3Control.cpp index 3043bfdd3..26048d67a 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -558,6 +558,7 @@ class V3ControlResolver final { uint8_t m_mode = NONE; std::unordered_map m_hierWorkers; FileLine* m_profileFileLine = nullptr; + std::map m_instrCfg; V3ControlResolver() = default; ~V3ControlResolver() = default; @@ -622,6 +623,41 @@ public: return cost; } } + // Helper for adding targets to the instrumentation config map + std::pair splitPrefixAndVar(const string& target) { + auto pos = target.rfind('.'); + if (pos == string::npos) { + // No prefix, return error + } + string prefix = target.substr(0, pos); + string varTarget = target.substr(pos + 1); + return {prefix, varTarget}; + } + // Add the instrumentation config data to the map to create the initial map (Used in verilog.y) + void addInstrumentationConfigs(FileLine* fl, const string& instrFunction, int instrID, + const string& target) { + // Error MSG if the instrumentation of the top module is not possible + if ((std::count(target.begin(), target.end(), '.') < 2)) { + v3fatal("In .vlt defined target tries to instrument the highest MODULE, is not possible!" + " ... Target string: " << target); + } + // Implement custom iterator to remove the last part of the target and insert it into the vector of the map + // If the target string is the same as one already in the map, push the var to the vector + auto [prefix, varTarget] = splitPrefixAndVar(target); + InstrumentationEntry entry{instrID, instrFunction, varTarget}; + auto it = m_instrCfg.find(prefix); + if (it != m_instrCfg.end()) { + it->second.entries.push_back(entry); + } else { + // Create a new entry in the map + InstrumentationTarget newTarget; + newTarget.entries.push_back(entry); + m_instrCfg[prefix] = std::move(newTarget); + } + } + std::map& getInstrumentationConfigs() { + return m_instrCfg; + } }; //###################################################################### @@ -680,6 +716,11 @@ void V3Control::addModulePragma(const string& module, VPragmaType pragma) { V3ControlResolver::s().modules().at(module).addModulePragma(pragma); } +void V3Control::addInstrumentationConfigs(FileLine* fl, const string& instrumentationfunc, + int instrID, const string& target) { + V3ControlResolver::s().addInstrumentationConfigs(fl, instrumentationfunc, instrID, target); +} + void V3Control::addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) { V3ControlResolver::s().addProfileData(fl, hierDpi, cost); } @@ -810,6 +851,9 @@ int V3Control::getHierWorkers(const string& model) { FileLine* V3Control::getHierWorkersFileLine(const string& model) { return V3ControlResolver::s().getHierWorkersFileLine(model); } +std::map& V3Control::getInstrumentationConfigs() { + return V3ControlResolver::s().getInstrumentationConfigs(); +} uint64_t V3Control::getProfileData(const string& hierDpi) { return V3ControlResolver::s().getProfileData(hierDpi); } diff --git a/src/V3Control.h b/src/V3Control.h index 1d1685f46..72deec09b 100644 --- a/src/V3Control.h +++ b/src/V3Control.h @@ -26,6 +26,31 @@ #include "V3Mutex.h" //###################################################################### +struct LengthThenLexiographic final { + // Used to sort strings by length, then lexicographically + bool operator()(const string& a, const string& b) const { + if (a.length() != b.length()) return a.length() < b.length(); + return a < b; + } +}; +struct InstrumentationEntry final { + int instrID; + std::string instrFunc; + std::string varTarget; + AstVar* origVarps; + AstVar* instrVarps; +}; +struct InstrumentationTarget final { + std::vector entries; + AstModule* origModulep; + AstModule* instrModulep; + AstModule* topModulep; + AstModule* pointingModulep; + AstCell* cellp; + bool processed = false; + bool done = false; + bool multipleCellps = false; +}; class V3Control final { public: @@ -38,6 +63,9 @@ public: static void addIgnoreMatch(V3ErrorCode code, const string& filename, const string& contents, const string& match); static void addInline(FileLine* fl, const string& module, const string& ftask, bool on); + static void addInstrumentationConfigs(FileLine* fl, const string& instrumentationfunc, + int instrID, const string& target); + static std::map& getInstrumentationConfigs(); static void addModulePragma(const string& module, VPragmaType pragma); static void addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost); static void addProfileData(FileLine* fl, const string& model, const string& key, diff --git a/src/V3Instrumentation.cpp b/src/V3Instrumentation.cpp new file mode 100644 index 000000000..8d0dc523a --- /dev/null +++ b/src/V3Instrumentation.cpp @@ -0,0 +1,1098 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: +// +// 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 +// +//************************************************************************* +// V3Instrumentation's Transformations: +// The instrumentation configuration map is populated with the relevant nodes, as defined by the +// target string specified in the instrumentation configuration within the .vlt file. +// Additionally, the AST (Abstract Syntax Tree) is modified to insert the necessary extra nodes +// required for instrumentation. +// Furthermore, the links between Module, Cell, and Var nodes are adjusted to ensure correct +// connectivity for instrumentation purposes. +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Instrumentation.h" + +#include "V3Control.h" +#include "V3File.h" + +#include +#include +#include +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +//################################################################################## +// Instrumentation class finder +class InstrumentationTargetFinder final : public VNVisitor { + AstNetlist* m_netlist = nullptr; // Enable traversing from the beginning if the visitor is to deep + AstNodeModule* m_cellModp = nullptr; // Stores the modulep of a Cell node + AstModule* m_modp = nullptr; // Stores the current modulep the visitor is looking at + AstModule* m_targetModp = nullptr; // Stores the targeted modulep + bool m_error = false; // Displays if there was already an error message earlier + bool m_foundCellp = false; // If the visitor found the relevant instance + bool m_foundModp = false; // If the visitor found the relevant model + bool m_foundVarp = false; // If the visitor found the relevant variable + bool m_initModp = true; // If the visitor is in the first module node of the netlist + size_t m_instrIdx = 0; + string m_currHier; // Stores the current hierarchy of the visited nodes (Module, Cell, Var) + string m_target; // Stores the currently visited target string from the config map + + // METHODS + //---------------------------------------------------------------------------------- + AstModule* findModp(AstNetlist* netlist, AstModule* modp) { + for (AstNode* n = netlist->op1p(); n; n = n->nextp()) { + if (VN_IS(n, Module) && VN_CAST(n, Module) == modp) { return VN_CAST(n, Module); } + } + return nullptr; + } + // Helper function to compare the target string starts with the given prefix + bool cmpPrefix(const string& prefix, const string& target ) { + if (target.compare(0, prefix.size(), prefix) == 0 + && (target.size() == prefix.size() || target[prefix.size()] == '.')) { + return true; + } + return false; + } + // Helper function to check if a parameter was already added to the tree previously + bool hasParam(AstModule* modp) { + for (AstNode* n = modp->op2p(); n; n = n->nextp()) { + if (n->name() == "INSTRUMENT") { + return true; + } + } + return false; + } + // Helper function to check if a pin was already added to the tree previously + bool hasPin(AstCell* cellp) { + for (AstNode* n = cellp->paramsp(); n; n = n->nextp()) { + if (n->name() == "INSTRUMENT") { + return true; + } + } + return false; + } + // Check if the multipleCellps flag is set for the given target + bool hasMultiple(const std::string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + return it->second.multipleCellps; + } + return false; + } + // Check if the direct predecessor in the target string has been instrumented, + // to create the correct link between the already instrumented module and the current one. + bool hasPrior(AstModule* modulep, const string& target) { + const auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto priorTarget = reduce2Depth(split(target), KeyDepth::RelevantModule); + auto it = instrCfg.find(priorTarget); + return it != instrCfg.end() + && it->second.processed; + } + bool targetHasFullName(const string& fullname, const string& target) { + return fullname == target; + } + // Check if the given current Hierarchy matches the top module of the target (Pos: 0) + bool targetHasTop(const string& currHier, const string& target) { + return currHier == reduce2Depth(split(target), KeyDepth::TopModule); + } + // Check if the current hierarhy string matches the target string until the depth + // to the module that includes the cell/instance pointing to the targeted module + bool targetHasPointingMod(const string& pointingModuleName, const string& target) { + return pointingModuleName == reduce2Depth(split(target), KeyDepth::RelevantModule); + } + // Check if the given prefix matches the beginning of the current target string + bool targetHasPrefix(const string& prefix, const string& target) { + return cmpPrefix(prefix, target); + } + // Helper Function to split a string by '.' and return a vector of tokens + std::vector split(const std::string& str) { + static const std::regex dot_regex("\\."); + std::sregex_token_iterator iter(str.begin(), str.end(), dot_regex, -1); + std::sregex_token_iterator end; + return std::vector(iter, end); + } + // Helper function to reduce a given key to a certain hierarchy level. + enum class KeyDepth { + TopModule = 0, + RelevantModule = 1, + Instance = 2, + FullKey = 3 + }; + string reduce2Depth(std::vector keyTokens, KeyDepth hierarchyLevel) { + std::string reducedKey = keyTokens[0]; + if (hierarchyLevel == KeyDepth::TopModule) { + return keyTokens[0]; + } else { + int d = static_cast(hierarchyLevel); + for (size_t i = 1; i < keyTokens.size() - d; ++i) { + reducedKey += "." + keyTokens[i]; + } + return reducedKey; + } + } + // Helper function for adding the parameters into the tree + void addParam(AstModule* modp) { + AstVar* paramp = new AstVar(modp->fileline(), VVarType::GPARAM, "INSTRUMENT", + VFlagChildDType{}, nullptr); + paramp->valuep( + new AstConst(modp->fileline(), AstConst::Signed32{}, 0)); + paramp->dtypep(paramp->valuep()->dtypep()); + paramp->ansi(true); + modp->addStmtsp(paramp); + } + // Helper function for adding the parameters into the tree + void addPin(AstCell* cellp, bool isInstrumentPath) { + int pinnum = 0; + if (isInstrumentPath) { + for (AstNode* n = cellp->pinsp(); n; n = n->nextp()) { pinnum++; } + AstPin* pinp + = new AstPin(cellp->fileline(), pinnum + 1, "INSTRUMENT", + // The pin is set to 1 to enable the instrumentation path + new AstConst(cellp->fileline(), AstConst::Signed32{}, 1)); + pinp->param(true); + cellp->addParamsp(pinp); + } else { + for (AstNode* n = cellp->pinsp(); n; n = n->nextp()) { pinnum++; } + AstPin* pinp + = new AstPin(cellp->fileline(), pinnum + 1, "INSTRUMENT", + new AstParseRef(cellp->fileline(), VParseRefExp::PX_TEXT, "INSTRUMENT")); + pinp->param(true); + cellp->addParamsp(pinp); + } + } + // Edit the instrumentation data for the cell in the map + void editInstrData(AstCell* cellp, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.cellp = cellp; + } + } + // Edit the instrumentation data for the pointing module in the map + void editInstrData(AstModule* modulep, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.pointingModulep = modulep; + } + } + // Check for multiple cells pointing to the next module + void multCellForModp(AstCell* cellp) { + std::multiset cellModps; + for (AstNode* n = m_modp->op2p(); n; n = n->nextp()) { + if (VN_IS(n, Cell)) { cellModps.insert(VN_CAST(n, Cell)->modp()); } + } + m_modp = nullptr; + m_cellModp = cellp->modp(); + auto modpRepetition = cellModps.count(m_cellModp); + if (modpRepetition > 1 + && !targetHasFullName(m_currHier, m_target)) { + setMultiple(m_target); + } + } + // Insert the cell node that is/will pointing/point to the targeted module + void setCell(AstCell* cellp, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.cellp = cellp; + } + } + // Insert the original and instrumented module nodes to the map + void setInstrModule(AstModule* origModulep, AstModule* instrModulep, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.origModulep = origModulep; + it->second.instrModulep = instrModulep; + } + } + // Set the multipleCellps flag + void setMultiple(const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.multipleCellps = true; + } + } + // Insert the module node that includes the cell pointing to the targeted module + // to the map + void setPointingMod(AstModule* modulep, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.pointingModulep = modulep; + } + } + // Set the processed flag + void setProcessed(const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.processed = true; + } + } + // Insert the top module node of the netlist to the map + void setTopMod(AstModule* modulep, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + it->second.topModulep = modulep; + } + } + // Insert the original and instrumented variable nodes to the map + void setVar(AstVar* varp, AstVar* instVarp, const string& target) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + auto it = instrCfg.find(target); + if (it != instrCfg.end()) { + for (auto& entry : it->second.entries) { + if (entry.varTarget == varp->name()) { + entry.origVarps = varp; + entry.instrVarps = instVarp; + return; + } + } + } + } + + // VISITORS + //---------------------------------------------------------------------------------- + + //ASTMODULE VISITOR FUNCTION: + //Iterates over the existing module nodes in the netlist. + //For the first module in the netlist the node name is checked if it is at the first position in + //the target string provided by the configuration file. If not an error is thown, otherwise the + //modules is checked for an already existing INSTRUMENT parameter. If there is no INSTRUMENT + //parameter present we add it to the module. This parameter is used to control the + //instrumentation of the target. The module is then added to the map of the instrumentation + //configs as the top module. Additionally the hierarchy the function viewed is currently add is + //initialized with the module name. This module hierarchy is used to identify the correct target + //path in the netlist. The function iterates over the children of the module, with the Cells and + //Vars beeing the relevant targets. + + //After the iteration of the children the m_modp variable needs to be set by the Cell visitor to + //continue or there needs no suitable cell to be found. (See CELL VISITOR FUNCTION & VAR VISITOR + //FUNCTION) Since the module from the m_modp can appear earlier in the tree the fundModp function + //is used to iterate over the netlift from the beginning to find the module. The module node + //displayed by the m_modp variable is then checked if this is the module containing the target + //variable (relevant module) or if it the module containing the cell pointing to the relevant + //module (pointing module). If the module node suits one of these two conditions the module nodes + //are added to the instrumentation configs map. Independetly from these conditions the INSTRUMENT + //parameter is added to the module nodes in the target path. This parameter is used to control + //the instrumentation of the target. + void visit (AstModule* nodep) { + if (m_initModp) { + if (targetHasTop(nodep->name(), m_target)) { + m_foundModp = true; + m_modp = nodep; + m_currHier = nodep->name(); + if (!hasParam(nodep)) { + addParam(nodep); + } + if (string::npos == m_target.rfind('.')) { + m_targetModp = nodep; + m_foundCellp = true; // Set to true since there is no Instance that the cell visitor could find + } + setTopMod(nodep, m_target); + iterateChildren(nodep); + } else if (!m_foundModp && nodep->name() == "@CONST-POOL@") { + v3error("Verilator-configfile': could not find initial 'module' in 'module.instance.__'" + " ... Target: '" + << m_target << "'"); + m_initModp = false; + m_error = true; + } + } else if (m_cellModp != nullptr && (nodep = findModp(m_netlist, VN_CAST(m_cellModp, Module))) != nullptr) { + if (targetHasFullName(m_currHier, m_target)) { + AstModule* instrModp = nullptr; + m_foundModp = true; + m_targetModp = nodep; + m_cellModp = nullptr; + // Check for prior changes made to the tree + if (hasPrior(nodep, m_currHier)) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + instrModp = instrCfg.find(reduce2Depth(split(m_currHier), KeyDepth::RelevantModule))->second.instrModulep; + editInstrData(instrModp, m_currHier); + AstCell* cellp = nullptr; + for (AstNode* n = instrModp->op2p(); n; n = n->nextp()) { + if (VN_IS(n, Cell) && (VN_CAST(n, Cell)->modp() == nodep) && instrCfg.find(m_currHier)->second.cellp->name() == n->name()) { + cellp = VN_CAST(n, Cell); + break; + } + } + editInstrData(cellp, m_currHier); + } + if (!hasParam(nodep)) { + addParam(nodep); + } + instrModp = nodep->cloneTree(false); + instrModp->name(nodep->name() + "__inst__" + std::to_string(m_instrIdx)); + if (hasMultiple(m_target)) { instrModp->inLibrary(true); } + setInstrModule(nodep, instrModp, m_target); + iterateChildren(nodep); + } else if (targetHasPointingMod(m_currHier, m_target)) { + m_foundModp = true; + m_foundCellp = false; + m_modp = nodep; + m_cellModp = nullptr; + if (!hasParam(nodep)) { + addParam(nodep); + } + setPointingMod(nodep, m_target); + iterateChildren(nodep); + } else if (targetHasPrefix(m_currHier, m_target)) { + m_foundModp = true; + m_foundCellp = false; + m_modp = nodep; + m_cellModp = nullptr; + if (!hasParam(nodep)) { + addParam(nodep); + } + iterateChildren(nodep); + } + } else if(!m_error && !m_foundCellp) { + v3error("Verilator-configfile: could not find 'instance' in " + "'__.instance.__' ... Target string: '" + << m_target << "'"); + } else if(!m_error && !m_foundVarp) { + v3error("Verilator-configfile': could not find '.var' in '__.module.var'" + " ... Target: '" + << m_target << "'"); + } + } + + //ASTCELL VISITOR FUNCTION: + //This cell visitor function is called if the module visitor function found a module that matches + //the target string from the config. The first function call should be when visiting the initial + //module in the netlist. When a cell is found that matches the target string and is not marked as + //found, the current hierarchy is updated and the cell marked as found. Additionally, if this is + //the cell in the initial module, the initial module flag is set to false. The in the current + //module existing cells are checked if there are multiple cells linking to the next module in the + //target string. After that the m_modp is updated to match the cell's module pointer, which is + //needed for the next call of the module visitor. Next the pin for the INSTRUMENT parameter is + //added to the cell. This parameter is added either as a constant or as a reference, depending on + //the traversal stage. If there are multiple cells linking to the next module in the target + //string, the multiple flag is set in the instrumentation config map. For the inistial module the + //found cell is then added to the instrumentation configuration map with the current hierarchy as + //the target path. Otherwise the cell is added to the instrumentation configuration map, when the + //current hierarchy with the cell name fully matches a target path, with the last two entrances + //removed (Module, Var). This function ensures that the correct cells in the design hierarchy are + //instrumented and tracked, supporting both unique and repeated module instances. + void visit (AstCell* nodep) { + if (m_initModp) { + if (targetHasFullName(m_currHier + "." + nodep->name(), m_target)) { + m_foundCellp = true; + m_foundModp = false; + m_initModp = false; + m_currHier = m_currHier + "." + nodep->name(); + if (!hasPin(nodep)) { + addPin(nodep, false); + } + multCellForModp(nodep); + setCell(nodep, m_target); + } else if (targetHasPrefix(m_currHier + "." + nodep->name(), m_target)) { + m_foundCellp = true; + m_foundModp = false; + m_initModp = false; + m_currHier = m_currHier + "." + nodep->name(); + if (!hasPin(nodep)) { + addPin(nodep, true); + } + multCellForModp(nodep); + setCell(nodep, m_target); + } else if (!m_foundCellp && !VN_IS(nodep->nextp(), Cell)) { + v3error("Verilator-configfile': could not find initial 'instance' in " + "'topModule.instance.__' ... Target string: '" + << m_target << "'"); + m_error = true; + m_initModp = false; + } + } else if (m_modp != nullptr && targetHasFullName(m_currHier + "." + nodep->name(), m_target)) { + m_foundCellp = true; + m_foundModp = false; + m_currHier = m_currHier + "." + nodep->name(); + if (!hasPin(nodep)) { + addPin(nodep, false); + } + multCellForModp(nodep); + setCell(nodep, m_target); + } else if (m_modp != nullptr && targetHasPrefix(m_currHier + "." + nodep->name(), m_target)) { + m_foundCellp = true; + m_foundModp = false; + m_currHier = m_currHier + "." + nodep->name(); + if (!hasPin(nodep)) { + addPin(nodep, false); + } + multCellForModp(nodep); + } + } + + //ASTVAR VISITOR FUNCTION: + //The var visitor function is used to find the variable that matches the target string from the + //config. This is only done if the Cell visitor does not find a matching cell in the current + //module of the target hierarchy. Since we therefore know that we will not traverse any further + //in the hierarchy of the model, we can check for this variable. If a variable is found, with its + //name added to the current hierarchy, that siuts the target string, an edited version and the + //original version are added to the instrumentation config map. + void visit (AstVar* nodep) { + if (m_targetModp != nullptr) { + const InstrumentationTarget& target = V3Control::getInstrumentationConfigs().find( + m_currHier)->second; + for (const auto& entry : target.entries) { + if (nodep->name() == entry.varTarget) { + int width; + AstBasicDType* basicp = nodep->basicp(); + bool literal = basicp->isLiteralType(); + bool implicit = basicp->implicit(); + if (!implicit) { + // Since the basicp is not implicit, there should be a rangep indicating the width + width = nodep->basicp()->rangep()->elementsConst(); + } + bool isUnsupportedType = !literal && !implicit; + bool isUnsupportedWidth = literal && width > 64; + if (isUnsupportedType || isUnsupportedWidth) { + v3error("Verilator-configfile: target variable '" + << nodep->name() << "' in '" + << m_currHier << "' must be a supported type!"); + return; + } + AstVar* varp = nodep->cloneTree(false); + varp->name("tmp_" + nodep->name()); + varp->origName("tmp_" + nodep->name()); + varp->trace(true); + if (varp->varType() == VVarType::WIRE) { + varp->varType(VVarType::VAR); + } + setVar(nodep, varp, m_target); + if (string::npos == m_currHier.rfind('.')) { + AstModule* modulep = m_modp->cloneTree(false); + modulep->name(m_modp->name() + "__inst__" + std::to_string(m_instrIdx)); + setInstrModule( + m_modp, modulep, m_currHier); + m_initModp = false; + } + m_foundVarp = true; + } + } + } + }; + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTOR + //------------------------------------------------------------------------------- + explicit InstrumentationTargetFinder(AstNetlist* nodep) { + const auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (const auto& pair : instrCfg) { + m_netlist = nodep; + m_target = pair.first; + m_initModp = true; + m_currHier = ""; + iterate(nodep); + setProcessed(m_target); + m_foundModp = false; + m_foundCellp = false; + m_foundVarp = false; + m_error = false; + m_targetModp = nullptr; + m_modp = nullptr; + m_instrIdx++; + } + }; + ~InstrumentationTargetFinder() override = default; +}; + +//################################################################################## +// Instrumentation class functions +class InstrumentationFunction final : public VNVisitor { + bool m_assignw = false; // Flag if a assignw exists in the netlist + bool m_addedport = false; // Flag if a port was already added + bool m_addedTask = false; // Flag if a task was already added + bool m_addedFunc = false; // Flag if a function was already added + bool m_interface = false; // Flag if the ParseRef node is part of an interface + int m_pinnum = 0; // Pinnumber for the new Port nodes + string m_targetKey; // Stores the target string from the instrumentation config + string m_task_name; + size_t m_targetIndex = 0; // Index of the target variable in the instrumentation config + AstAlways* m_alwaysp = nullptr; // Stores the added always node + AstAssignW* m_assignwp = nullptr; // Stores the added assignw node + AstGenBlock* m_instGenBlock = nullptr; // Store the GenBlock node for instrumentation hierarchy check + AstTask* m_taskp = nullptr; // // Stores the created task node + AstFunc* m_funcp = nullptr; // Stores the created function node + AstFuncRef* m_funcrefp = nullptr; // Stores the created funcref node + AstTaskRef* m_taskrefp = nullptr; // Stores the created taskref node + AstModule* m_current_module = nullptr; // Stores the currenty visited module + AstModule* m_current_module_cell_check = nullptr; // Stores the module node(used by cell visitor) + AstVar* m_tmp_varp = nullptr; // Stores the instrumented variable node + AstVar* m_orig_varp = nullptr; // Stores the original variable node + AstVar* m_orig_varp_instMod = nullptr; // Stores the original variable node in instrumented module node + AstPort* m_orig_portp = nullptr; // Stores the original port node + + // METHODS + //---------------------------------------------------------------------------------- + // Find the relevant instrumentation config in the map corresponding to the given key + const InstrumentationTarget* getInstrCfg(const std::string& key) { + const auto& map = V3Control::getInstrumentationConfigs(); + auto instrCfg = map.find(key); + if (instrCfg != map.end()) { + return &instrCfg->second; + } else { + return nullptr; + } + } + // Get the Cell nodep pointer from the configuration map for the given key + AstCell* getMapEntryCell(const std::string& key) { + if (auto cfg = getInstrCfg(key)) { + return cfg->cellp; + } + return nullptr; + } + // Get the instrumented Module node pointer from the configuration map for the given key + AstModule* getMapEntryInstModule(const std::string& key) { + if (auto cfg = getInstrCfg(key)) { + return cfg->instrModulep; + } + return nullptr; + } + // Get the Module node pointer pointing to the instrumented/original module from the + // configuration map for the given key + AstModule* getMapEntryPointingModule(const std::string& key) { + if (auto cfg = getInstrCfg(key)) { + return cfg->pointingModulep; + } + return nullptr; + } + // Get the instrumented variable node pointer from the configuration map for the given key + AstVar* getMapEntryInstVar(const std::string& key, size_t index) { + if (auto cfg = getInstrCfg(key)) { + const auto& entries = cfg->entries; + if (index < entries.size()) { + return entries[index].instrVarps; + } + } + return nullptr; + } + // Get the original variable node pointer from the configuration map for the given key + AstVar* getMapEntryVar(const std::string& key, size_t index) { + if (auto cfg = getInstrCfg(key)) { + const auto& entries = cfg->entries; + if (index < entries.size()) { + return entries[index].origVarps; + } + } + return nullptr; + } + // Check if the given module node pointer is an instrumented module entry in the configuration + // map for the given key + bool isInstModEntry(AstModule* nodep, const std::string& key) { + const auto& map = V3Control::getInstrumentationConfigs(); + const auto instrCfg = map.find(key); + if (instrCfg != map.end() + && instrCfg->second.instrModulep == nodep) { + return true; + } else { + return false; + } + } + // Check if the given module node pointer is the top module entry in the configuration map + bool isTopModEntry(AstModule* nodep) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (const auto& pair : instrCfg) { + if (nodep == pair.second.topModulep) { return true; } + } + return false; + } + // Check if the given module node pointer is the pointing module entry in the configuration map + bool isPointingModEntry(AstModule* nodep) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (const auto& pair : instrCfg) { + if (nodep == pair.second.pointingModulep) { return true; } + } + return false; + } + // Check if the given module node pointer has already been instrumented/done flag has been set + bool isDone(AstModule* nodep) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (const auto& pair : instrCfg) { + if (nodep == pair.second.instrModulep) { return pair.second.done; } + } + return true; + } + // Check if the multipleCellps flag is set for the given key in the configuration map + bool hasMultiple(const std::string& key) { + const auto& map = V3Control::getInstrumentationConfigs(); + const auto instrCfg = map.find(key); + if (instrCfg != map.end()) { + return instrCfg->second.multipleCellps; + } else { + return false; + } + } + // Get the fault case for the given key in the configuration map + int getMapEntryFaultCase(const std::string& key, size_t index) { + const auto& map = V3Control::getInstrumentationConfigs(); + const auto instrCfg = map.find(key); + if (instrCfg != map.end()) { + const auto& entries = instrCfg->second.entries; + if (index < entries.size()) { + return entries[index].instrID; + } + return -1; // Return -1 if index is out of bounds + } else { + return -1; + } + } + // Get the instrumentation function name for the given key in the configuration map + string getMapEntryFunction(const std::string& key, size_t index) { + const auto& map = V3Control::getInstrumentationConfigs(); + const auto instrCfg = map.find(key); + if (instrCfg != map.end()) { + const auto& entries = instrCfg->second.entries; + if (index < entries.size()) { + return entries[index].instrFunc; + } + return ""; + } else { + return ""; + } + } + // Set the done flag for the given module node pointer in the configuraiton map + void setDone(AstModule* nodep) { + auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (auto& pair : instrCfg) { + if (nodep == pair.second.instrModulep) { pair.second.done = true; } + } + } + AstNode* createDPIInterface(AstModule* nodep, AstVar* orig_varp, const string& task_name) { + AstBasicDType* basicp = nullptr; + if (orig_varp->basicp()->isLiteralType() || orig_varp->basicp()->implicit()) { + int width; + if (orig_varp->basicp()->implicit()) { + // Since Var is implicit set/assume the width as 1 like in V3Width.cpp in the AstVar visitor + width = 1; + } else { + width = orig_varp->basicp()->rangep()->elementsConst(); + } + if (width <= 1) { + basicp = new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::BIT}; + } else if (width <= 8) { + basicp = new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::BYTE}; + } else if (width <= 16) { + basicp = new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::SHORTINT}; + } else if (width <= 32) { + basicp = new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT}; + } else if (width <= 64) { + basicp = new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::LONGINT}; + } + return new AstFunc{nodep->fileline(), m_task_name, nullptr, basicp}; + } else { + return new AstTask{nodep->fileline(), m_task_name, nullptr}; + } + } + + // Visitors + //---------------------------------------------------------------------------------- + + //ASTNETLIST VISITOR FUNCTION: + //Loop over map entries for module nodes and add them to the tree + void visit(AstNetlist* nodep) { + const auto& instrCfg = V3Control::getInstrumentationConfigs(); + for (const auto& pair : instrCfg) { + nodep->addModulesp(pair.second.instrModulep); + m_targetKey = pair.first; + iterateChildren(nodep); + m_assignw = false; + } + } + + //ASTMODULE VISITOR FUNCTION: + //This function is called for each module node in the netlist. + //It checks if the module node is part of the instrumentation configuratio map. + //Depending on the type of the module node (Instrumented, Top, Pointing, or Original), + //it performs different actions: + // - If the module is an instrumented module entry and has not been done, it creates a new + //task for the instrumentation function, adds the temporary variable, and creates a task + //reference to the instrumentation function. + // - If the module is a pointing module or a top module and has no multiple cellps, it checks + //the cell for the target key and counts the pins. This pin count is used in the CELL VISITOR + //FUNCTION to set a siutable pin number for the INSTRUMENT parameter. Look there fore further + //information. + // - If the module is a pointing module and has multiple cellps, it creates a begin block with + //a conditional statement to select between the instrumented and original cell. + // Additionally like in the previous case, the pin count is used to set a suitable pin + //number for the INSTRUMENT parameter.\ Since the cell which need to be edited are located not in + //the original module, but in the pointing/top module, the current_module_cell_check variable is + //set to the module visited by the function and fulfilling this condition. + void visit(AstModule* nodep) { + const InstrumentationTarget& target = V3Control::getInstrumentationConfigs().find( + m_targetKey)->second; + const auto& entries = target.entries; + for (m_targetIndex = 0; m_targetIndex < entries.size(); ++m_targetIndex) { + const auto& entry = entries[m_targetIndex]; + m_tmp_varp = getMapEntryInstVar(m_targetKey, m_targetIndex); + m_orig_varp = getMapEntryVar(m_targetKey, m_targetIndex); + m_task_name = getMapEntryFunction(m_targetKey, m_targetIndex); + if (isInstModEntry(nodep, m_targetKey) && !isDone(nodep)) { + m_current_module = nodep; + + for (AstNode* n = nodep->op2p(); n; n = n->nextp()) { + if (VN_IS(n, Task) && n->name() == m_task_name) { + m_taskp = VN_CAST(n, Task); + m_addedTask = true; + break; + } + if (VN_IS(n, Func) && n->name() == m_task_name) { + m_funcp = VN_CAST(n, Func); + m_addedFunc = true; + break; + } + } + if (!m_addedTask && !m_addedFunc) { + auto m_dpip = createDPIInterface(nodep ,m_orig_varp, m_task_name); + if (VN_IS(m_dpip, Func)) { + m_funcp = VN_CAST(m_dpip, Func); + m_funcp->dpiImport(true); + m_funcp->prototype(true); + nodep->addStmtsp(m_funcp); + } + if (VN_IS(m_dpip, Task)) { + m_taskp = VN_CAST(m_dpip, Task); + m_taskp->dpiImport(true); + m_taskp->prototype(true); + nodep->addStmtsp(m_taskp); + } + } + if (m_orig_varp->direction() == VDirection::INPUT) { + m_tmp_varp->varType(VVarType::VAR); + m_tmp_varp->direction(VDirection::NONE); + m_tmp_varp->trace(true); + } + nodep->addStmtsp(m_tmp_varp); + + if (m_taskp != nullptr) { + m_taskrefp = new AstTaskRef{ + nodep->fileline(), m_task_name, + new AstArg{nodep->fileline(), m_tmp_varp->name(), + new AstVarRef{nodep->fileline(), m_tmp_varp, VAccess::WRITE}}}; + m_taskrefp->taskp(m_taskp); + m_alwaysp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, nullptr, nullptr}; + nodep->addStmtsp(m_alwaysp); + } + if (m_funcp != nullptr) { + m_funcrefp = new AstFuncRef{nodep->fileline(), m_funcp, nullptr}; + m_assignwp = new AstAssignW{nodep->fileline(), + new AstParseRef{nodep->fileline(), VParseRefExp::PX_TEXT, m_tmp_varp->name()}, + m_funcrefp}; + nodep->addStmtsp(m_assignwp); + } + + if (m_targetIndex == entries.size() - 1) { setDone(nodep); } + for (AstNode* n = nodep->op2p(); n; n = n->nextp()) { + if (VN_IS(n, Port)) { + m_pinnum = VN_CAST(n, Port)->pinNum(); + } + } + iterateChildren(nodep); + } else if ((std::count(m_targetKey.begin(), m_targetKey.end(), '.') > 0) + && (isPointingModEntry(nodep) || isTopModEntry(nodep)) + && !hasMultiple(m_targetKey)) { + m_current_module_cell_check = nodep; + AstCell* instCellp = getMapEntryCell(m_targetKey); + for (AstNode* n = instCellp->pinsp(); n; n = n->nextp()) { m_pinnum++; } + iterateChildren(nodep); + } else if (isPointingModEntry(nodep) && hasMultiple(m_targetKey)) { + m_current_module_cell_check = nodep; + AstCell* instCellp = getMapEntryCell(m_targetKey)->cloneTree(false); + instCellp->modp(getMapEntryInstModule(m_targetKey)); + for (AstNode* n = instCellp->pinsp(); n; n = n->nextp()) { m_pinnum++; } + m_instGenBlock = new AstGenBlock{nodep->fileline(), "", instCellp, false}; + AstGenIf* genifp = new AstGenIf{ + nodep->fileline(), + new AstParseRef{nodep->fileline(), VParseRefExp::PX_TEXT, "INSTRUMENT"}, + m_instGenBlock, + new AstGenBlock{nodep->fileline(), "", getMapEntryCell(m_targetKey)->cloneTree(false), + false}}; + + nodep->addStmtsp(genifp); + iterateChildren(m_instGenBlock); + iterateChildren(nodep); + } + m_current_module = nullptr; + m_current_module_cell_check = nullptr; + m_alwaysp = nullptr; + m_taskp = nullptr; + m_taskrefp = nullptr; + m_addedTask = false; + m_funcp = nullptr; + m_funcrefp = nullptr; + m_addedFunc = false; + m_addedport = false; + m_instGenBlock = nullptr; + } + m_targetIndex = 0; + } + + //ASTPORT VISITOR FUNCTION: + //When the target variable is an ouput port, this function is called. + //If no port is added yet, two new ports are added to the current module. + //This enabled the instrumentation of the ouput port and link this instrumented port to the + //modules reading from the original port. The idea behind this function is to set the + //instrumented port on the position of the original port in the module and move the original port + //to another pin number. + //This should ensure the linking over the name and the port position in the module should work. + void visit(AstPort* nodep) { + if (m_current_module != nullptr && m_orig_varp->direction() == VDirection::OUTPUT + && nodep->name() == m_orig_varp->name() && !m_addedport) { + m_orig_portp = nodep->cloneTree(false); + nodep->unlinkFrBack(); + nodep->deleteTree(); + m_current_module->addStmtsp( + new AstPort{nodep->fileline(), m_orig_portp->pinNum(), m_tmp_varp->name()}); + m_current_module->addStmtsp( + new AstPort{nodep->fileline(), m_pinnum + 1, m_orig_portp->name()}); + m_addedport = true; + } + } + + //ASTCELL VISITOR FUNCTION: + //This function visits the cell nodes in the module pointing to the instrumented module. + //Depending if hasMultiple is set for the target key, two different actions are performed: + // - If hasMultiple is false, the cell is modified to link to the instrumented module and the + //children are iterated. This ensures that the instrumented mopdule is used in the cell. Also if + //the original variable is an output variable, the children of this cell nodes are visited by the + //ASTPIN VISITOR FUNCTION. + // - If hasMultiple is true, the cell is unlinked from the back and deleted. + // This ensures that the cell is not used anymore in the module, and the conditional + //statment deciding between the instrumented and the original cell can be created/used. A third + //action is performed if the variable beeing instrumented is an ouput variable. In this case the + //children of this cell nodes are visited by the ASTPIN VISITOR FUNCTION. + void visit(AstCell* nodep) { + if (m_current_module_cell_check != nullptr && !hasMultiple(m_targetKey) + && nodep == getMapEntryCell(m_targetKey)) { + nodep->modp(getMapEntryInstModule(m_targetKey)); + if (m_orig_varp->direction() == VDirection::OUTPUT) { iterateChildren(nodep); } + } else if (m_current_module_cell_check != nullptr && hasMultiple(m_targetKey) + && nodep == getMapEntryCell(m_targetKey)) { + nodep->unlinkFrBack(); + nodep->deleteTree(); + } else if (m_instGenBlock != nullptr && nodep->modp() == getMapEntryInstModule(m_targetKey) + && m_orig_varp->direction() == VDirection::OUTPUT) { + iterateChildren(nodep); + } else if (m_current_module != nullptr && m_orig_varp->direction() == VDirection::INPUT) { + iterateChildren(nodep); + } + } + + //ASTPIN VISITOR FUNCTION: + //The function is used to change the pin name of the original variable to the instrumented + //variable name. This is done to ensure that the pin is correctly linked to the instrumented + //variable in the cell. + void visit(AstPin* nodep) { + if (nodep->name() == m_orig_varp->name() && m_orig_varp->direction() == VDirection::INPUT) { + iterateChildren(nodep); + } else if (nodep->name() == m_orig_varp->name()) { + nodep->name(m_tmp_varp->name()); + } + } + + //ASTTASK VISITOR FUNCTION: + //The function is used to further specify the task node created at the module visitor. + void visit(AstTask* nodep) { + if (m_addedTask == false && nodep == m_taskp && m_current_module != nullptr) { + AstVar* instrID = nullptr; + AstVar* var_x_task = nullptr; + AstVar* tmp_var_task = nullptr; + + instrID = new AstVar{nodep->fileline(), VVarType::PORT, "instrID", VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT, + VSigning::SIGNED, 32, 0}}; + instrID->direction(VDirection::INPUT); + + var_x_task = m_orig_varp->cloneTree(false); + var_x_task->varType(VVarType::PORT); + var_x_task->direction(VDirection::INPUT); + + tmp_var_task = m_tmp_varp->cloneTree(false); + tmp_var_task->varType(VVarType::PORT); + tmp_var_task->direction(VDirection::OUTPUT); + + nodep->addStmtsp(instrID); + nodep->addStmtsp(var_x_task); + nodep->addStmtsp(tmp_var_task); + } + } + + //ASTFUNC VISITOR FUNCITON: + //The function is used to further specify the function node created at the module visitor. + void visit(AstFunc* nodep) { + if (m_addedFunc == false && nodep == m_funcp && m_current_module != nullptr) { + AstVar* instrID = nullptr; + AstVar* var_x_func = nullptr; + + instrID = new AstVar{nodep->fileline(), VVarType::PORT, "instrID", VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT, + VSigning::SIGNED, 32, 0}}; + instrID->direction(VDirection::INPUT); + + var_x_func = m_orig_varp->cloneTree(false); + var_x_func->varType(VVarType::PORT); + var_x_func->direction(VDirection::INPUT); + + nodep->addStmtsp(instrID); + nodep->addStmtsp(var_x_func); + } + } + + //ASTALWAYS VISITOR FUNCTION: + //The function is used to add the task reference node to the always node and further specify the + //always node. + void visit(AstAlways* nodep) { + if (nodep == m_alwaysp && m_current_module != nullptr) { + AstBegin* newBegin = nullptr; + + m_taskrefp + = new AstTaskRef{nodep->fileline(), m_task_name, nullptr}; + + newBegin = new AstBegin{nodep->fileline(), "", + new AstStmtExpr{nodep->fileline(), m_taskrefp}, false}; + nodep->addStmtsp(newBegin); + } + iterateChildren(nodep); + } + + void visit(AstVar* nodep) { + if (m_current_module != nullptr && nodep->name() == m_orig_varp->name()) { + m_orig_varp_instMod = nodep; + } + } + + //ASTTASKREF VISITOR FUNCTION: + //The function is used to further specify the task reference node called by the always node. + void visit(AstTaskRef* nodep) { + if (nodep == m_taskrefp && m_current_module != nullptr) { + AstConst* constp_id = nullptr; + + constp_id = new AstConst{nodep->fileline(), AstConst::Unsized32{}, + getMapEntryFaultCase(m_targetKey, m_targetIndex)}; + + AstVarRef* added_varrefp = new AstVarRef{nodep->fileline(), m_orig_varp_instMod, VAccess::READ}; + + nodep->addPinsp(new AstArg{nodep->fileline(), "", constp_id}); + nodep->addPinsp(new AstArg{nodep->fileline(), "", added_varrefp}); + nodep->addPinsp(new AstArg{ + nodep->fileline(), "", + new AstParseRef{nodep->fileline(), VParseRefExp::PX_TEXT, m_tmp_varp->name()}}); + m_orig_varp_instMod = nullptr; + } + } + + //ASTFUNCREF VISITOR FUNCTION: + //The function is used to further specify the function reference node called by the assignw node + void visit(AstFuncRef* nodep) { + if (nodep == m_funcrefp && m_current_module != nullptr) { + AstConst* constp_id = nullptr; + + constp_id = new AstConst{nodep->fileline(), AstConst::Unsized32{}, + getMapEntryFaultCase(m_targetKey, m_targetIndex)}; + + AstVarRef* added_varrefp = new AstVarRef{nodep->fileline(), m_orig_varp_instMod, VAccess::READ}; + + nodep->addPinsp(new AstArg{nodep->fileline(), "", constp_id}); + nodep->addPinsp(new AstArg{nodep->fileline(), "", added_varrefp}); + m_orig_varp_instMod = nullptr; + } + } + + //ASTASSIGNW VISITOR FUNCTION: + //Sets the m_assignw flag to true if the current module is not null. + //Necessary for the AstParseRef visitor function to determine if the current node is part of an + //assignment. + void visit(AstAssignW* nodep) { + if (m_current_module != nullptr) { + if (nodep != m_assignwp) { + m_assignw = true; + } + iterateChildren(nodep); + } + m_assignw = false; + m_interface = false; + } + + // These two function are used to circumvent the instrumentation of ParseRef nodes for interfaces + void visit(AstDot* nodep) { + if (m_current_module != nullptr) { + m_interface = true; + } + } + void visit(AstReplicate* nodep) { + if (m_current_module != nullptr) { + m_interface = true; + } + } + + //ASTPARSE REF VISITOR FUNCTION: + //The function is used to change the parseref nodes to link to the instrumented variable instead + //of the original variable. Depending on the direction of the original variable, different + //actions are performed: + // - If the original variable is not an output variable and the assignment is true, the + //parseref node is changed to link to the instrumented variable. This ensures that the + //instrumented variable is used in the assignment. + // - If the original variable is an input variable, every parseref node is changed to link to + //the instrumented variable. This ensures that the instrumented variable is used as the new + //input. + void visit(AstParseRef* nodep) { + if (m_current_module != nullptr && m_orig_varp != nullptr + && nodep->name() == m_orig_varp->name()) { + if (m_assignw && !m_interface && m_orig_varp->direction() != VDirection::OUTPUT) { + nodep->name(m_tmp_varp->name()); + } else if (m_orig_varp->direction() == VDirection::INPUT) { + nodep->name(m_tmp_varp->name()); + } + } + } + + //----------------- + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit InstrumentationFunction(AstNetlist* nodep) { iterate(nodep); } + ~InstrumentationFunction() override = default; +}; + +//################################################################################## +// Instrumentation class functions + +// Function to find instrumentation targets and additional information for the instrumentation +// process +void V3Instrumentation::findTargets(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { InstrumentationTargetFinder{nodep}; } + V3Global::dumpCheckGlobalTree("instrumentationFinder", 0, dumpTreeEitherLevel() >= 3); +} + +// Function for the actual instrumentation process +void V3Instrumentation::instrument(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { InstrumentationFunction{nodep}; } + V3Global::dumpCheckGlobalTree("instrumentationFunction", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3Instrumentation.h b/src/V3Instrumentation.h new file mode 100644 index 000000000..148672203 --- /dev/null +++ b/src/V3Instrumentation.h @@ -0,0 +1,33 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: +// +// 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_V3INSTRUMENTATION_H_ +#define VERILATOR_V3INSTRUMENTATION_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//========================================================================= + +class V3Instrumentation final { +public: + static void findTargets(AstNetlist* nodep) VL_MT_DISABLED; + static void instrument(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Options.cpp b/src/V3Options.cpp index ec431c3c6..45efc3437 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1919,6 +1919,8 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, addIncDirUser(parseFileArg(optdir, string{valp})); }); + DECL_OPTION("-instrument", OnOff, &m_instrument); + parser.finalize(); for (int i = 0; i < argc;) { diff --git a/src/V3Options.h b/src/V3Options.h index cb7f6df96..f67eb4f3c 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -306,6 +306,7 @@ private: bool m_waiverMultiline = false; // main switch: --waiver-multiline bool m_xInitialEdge = false; // main switch: --x-initial-edge bool m_xmlOnly = false; // main switch: --xml-only + bool m_instrument = false; // main switch: --instrument int m_buildJobs = -1; // main switch: --build-jobs, -j int m_coverageExprMax = 32; // main switch: --coverage-expr-max @@ -578,6 +579,7 @@ public: bool xmlOnly() const { return m_xmlOnly; } bool serializeOnly() const { return m_xmlOnly || m_jsonOnly; } bool topIfacesSupported() const { return lintOnly() && !hierarchical(); } + bool instrument() const { return m_instrument; } int buildJobs() const VL_MT_SAFE { return m_buildJobs; } int convergeLimit() const { return m_convergeLimit; } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index d0afe8b64..0b0890dab 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -63,6 +63,7 @@ #include "V3HierBlock.h" #include "V3Inline.h" #include "V3Inst.h" +#include "V3Instrumentation.h" #include "V3Interface.h" #include "V3Life.h" #include "V3LifePost.h" @@ -151,6 +152,14 @@ static void process() { v3Global.vlExit(0); } + // Instrument Design with the configurations given in .vlt file + if (v3Global.opt.instrument()) { + v3Global.dpi(true); + V3Instrumentation::findTargets(v3Global.rootp()); + V3Error::abortIfErrors(); + V3Instrumentation::instrument(v3Global.rootp()); + } + // Convert parseref's to varrefs, and other directly post parsing fixups V3LinkParse::linkParse(v3Global.rootp()); // Cross-link signal names diff --git a/src/verilog.l b/src/verilog.l index 35e98feb0..3184af011 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -123,6 +123,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "hier_params" { FL; return yVLT_HIER_PARAMS; } "hier_workers" { FL; return yVLT_HIER_WORKERS; } "inline" { FL; return yVLT_INLINE; } + "instrument" { FL; return yVLT_INSTRUMENT; } "isolate_assignments" { FL; return yVLT_ISOLATE_ASSIGNMENTS; } "lint_off" { FL; return yVLT_LINT_OFF; } "lint_on" { FL; return yVLT_LINT_ON; } @@ -149,6 +150,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} -?"-file" { FL; return yVLT_D_FILE; } -?"-function" { FL; return yVLT_D_FUNCTION; } -?"-hier-dpi" { FL; return yVLT_D_HIER_DPI; } + -?"-id" { FL; return yVLT_D_ID; } + -?"-instance" { FL; return yVLT_D_INSTANCE; } -?"-levels" { FL; return yVLT_D_LEVELS; } -?"-lines" { FL; return yVLT_D_LINES; } -?"-match" { FL; return yVLT_D_MATCH; } @@ -157,6 +160,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} -?"-mtask" { FL; return yVLT_D_MTASK; } -?"-rule" { FL; return yVLT_D_RULE; } -?"-scope" { FL; return yVLT_D_SCOPE; } + -?"-target" { FL; return yVLT_D_TARGET; } -?"-task" { FL; return yVLT_D_TASK; } -?"-var" { FL; return yVLT_D_VAR; } -?"-workers" { FL; return yVLT_D_WORKERS; } diff --git a/src/verilog.y b/src/verilog.y index 734dea2ce..ebdd86930 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -250,6 +250,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_HIER_PARAMS "hier_params" %token yVLT_HIER_WORKERS "hier_workers" %token yVLT_INLINE "inline" +%token yVLT_INSTRUMENT "instrument" %token yVLT_ISOLATE_ASSIGNMENTS "isolate_assignments" %token yVLT_LINT_OFF "lint_off" %token yVLT_LINT_ON "lint_on" @@ -276,6 +277,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_D_FILE "--file" %token yVLT_D_FUNCTION "--function" %token yVLT_D_HIER_DPI "--hier-dpi" +%token yVLT_D_ID "--id" +%token yVLT_D_INSTANCE "--instance" %token yVLT_D_LEVELS "--levels" %token yVLT_D_LINES "--lines" %token yVLT_D_MATCH "--match" @@ -284,6 +287,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_D_MTASK "--mtask" %token yVLT_D_RULE "--rule" %token yVLT_D_SCOPE "--scope" +%token yVLT_D_TARGET "--target" %token yVLT_D_TASK "--task" %token yVLT_D_VAR "--var" %token yVLT_D_WORKERS "--workers" @@ -7995,6 +7999,8 @@ vltItem: { /* Historical, now has no effect */ } | vltInlineFront vltDModuleE vltDFTaskE { V3Control::addInline($1, *$2, *$3, $1); } + | yVLT_INSTRUMENT yVLT_D_MODEL yaSTRING yVLT_D_ID yaINTNUM yVLT_D_TARGET yaSTRING + { V3Control::addInstrumentationConfigs($1, *$3, $5->toSInt(), *$7); } | yVLT_COVERAGE_BLOCK_OFF vltDFile { V3Control::addCoverageBlockOff(*$2, 0); } | yVLT_COVERAGE_BLOCK_OFF vltDFile yVLT_D_LINES yaINTNUM