// -*- 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); }