diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a08cb45c2..834bcedd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,6 +116,7 @@ set(HEADERS V3Inline.h V3Inst.h V3InstrCount.h + V3InsertHook.h V3Interface.h V3LangCode.h V3LanguageWords.h @@ -289,6 +290,7 @@ set(COMMON_SOURCES V3Inline.cpp V3Inst.cpp V3InstrCount.cpp + V3InsertHook.cpp V3Interface.cpp V3LibMap.cpp V3Life.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 74696c1b1..c10f42737 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -286,6 +286,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Inline.o \ V3Inst.o \ V3InstrCount.o \ + V3InsertHook.o \ V3Interface.o \ V3LibMap.o \ V3Life.o \ diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1009c3234..f472edfb6 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1913,6 +1913,7 @@ class AstVar final : public AstNode { bool m_substConstOnly : 1; // Only substitute if constant bool m_overridenParam : 1; // Overridden parameter by #(...) or defparam bool m_trace : 1; // Trace this variable + bool m_isHookInserted : 1; // This variable is instrumented for coverage bool m_isLatched : 1; // Not assigned in all control paths of combo always bool m_isForceable : 1; // May be forced/released externally from user C code bool m_isForcedByCode : 1; // May be forced/released from AstAssignForce/AstRelease @@ -1965,6 +1966,7 @@ class AstVar final : public AstNode { m_substConstOnly = false; m_overridenParam = false; m_trace = false; + m_isHookInserted = false; m_isLatched = false; m_isForceable = false; m_isForcedByCode = false; @@ -2109,6 +2111,8 @@ public: bool gotNansiType() { return m_gotNansiType; } void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasStrengthAssignment() { return m_hasStrengthAssignment; } + void hasHookInserted(bool flag) { m_isHookInserted = flag; } + bool isHookInserted() const { return m_isHookInserted; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } bool isHideLocal() const { return m_isHideLocal; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 6396a6f8a..d946ff897 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2822,6 +2822,8 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFunc(str, attrSFormat); dumpJsonBoolFunc(str, ignorePostWrite); dumpJsonBoolFunc(str, ignoreSchedWrite); + dumpJsonBoolFunc(str, isHookInserted); + dumpJsonNum(str, "width", width()); dumpJsonGen(str); } void AstScope::dump(std::ostream& str) const { diff --git a/src/V3Control.cpp b/src/V3Control.cpp index d715cd2fc..a079025d4 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -576,6 +576,7 @@ class V3ControlResolver final { uint8_t m_mode = NONE; std::unordered_map m_hierWorkers; FileLine* m_profileFileLine = nullptr; + std::map m_hookInsCfg; V3ControlResolver() = default; ~V3ControlResolver() = default; @@ -640,6 +641,45 @@ public: return cost; } } + // Helper for adding targets to the hook-insertion 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 hook-insertion config data to the map to create the initial map (Used in verilog.y) + void addHookInsCfg(FileLine* fl, const string& insFunction, int insID, const string& target) { + // Error MSG if the hook-insertion of the top module is not possible + if ((std::count(target.begin(), target.end(), '.') < 2)) { + v3fatal("In .vlt defined target tries to insert-hook to the highest MODULE, which 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 result = splitPrefixAndVar(target); + auto prefix = result.first; + auto varTarget = result.second; + HookInsertEntry entry{insID, insFunction, varTarget, {}, {}}; + auto it = m_hookInsCfg.find(prefix); + if (it != m_hookInsCfg.end()) { + it->second.entries.push_back(entry); + } else { + // Create a new entry in the map + HookInsertTarget newTarget; + newTarget.entries.push_back(entry); + m_hookInsCfg[prefix] = std::move(newTarget); + } + } + std::map& getHookInsCfg() { + return m_hookInsCfg; + } }; //###################################################################### @@ -698,6 +738,11 @@ void V3Control::addModulePragma(const string& module, VPragmaType pragma) { V3ControlResolver::s().modules().at(module).addModulePragma(pragma); } +void V3Control::addHookInsCfg(FileLine* fl, const string& insfunc, int insID, + const string& target) { + V3ControlResolver::s().addHookInsCfg(fl, insfunc, insID, target); +} + void V3Control::addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) { V3ControlResolver::s().addProfileData(fl, hierDpi, cost); } @@ -859,6 +904,9 @@ int V3Control::getHierWorkers(const string& model) { FileLine* V3Control::getHierWorkersFileLine(const string& model) { return V3ControlResolver::s().getHierWorkersFileLine(model); } +std::map& V3Control::getHookInsCfg() { + return V3ControlResolver::s().getHookInsCfg(); +} uint64_t V3Control::getProfileData(const string& hierDpi) { return V3ControlResolver::s().getProfileData(hierDpi); } diff --git a/src/V3Control.h b/src/V3Control.h index 8a7a9070f..3ba3cc8b8 100644 --- a/src/V3Control.h +++ b/src/V3Control.h @@ -26,6 +26,32 @@ #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 HookInsertEntry final { + int insID; + std::string insFunc; + std::string varTarget; + AstVar* origVarps; + AstVar* insVarps; + bool found = false; +}; +struct HookInsertTarget final { + std::vector entries; + AstModule* origModulep; + AstModule* insModulep; + AstModule* topModulep; + AstModule* pointingModulep; + AstCell* cellp; + bool processed = false; + bool done = false; + bool multipleCellps = false; +}; class V3Control final { public: @@ -44,6 +70,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 addHookInsCfg(FileLine* fl, const string& insfunc, int insID, + const string& target); + static std::map& getHookInsCfg(); 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/V3InsertHook.cpp b/src/V3InsertHook.cpp new file mode 100644 index 000000000..d4d2308b7 --- /dev/null +++ b/src/V3InsertHook.cpp @@ -0,0 +1,1048 @@ +// -*- 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 +// +//************************************************************************* +// V3HookInsert's Transformations: +// The hook-insertion configuration map is populated with the relevant nodes, as defined by the +// target string specified in the hook-insertion configuration within the .vlt file. +// Additionally, the AST (Abstract Syntax Tree) is modified to insert the necessary extra nodes +// required for hook-insertion. +// Furthermore, the links between Module, Cell, and Var nodes are adjusted to ensure correct +// connectivity for hook-insertion purposes. +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3InsertHook.h" + +#include "V3Control.h" +#include "V3File.h" + +#include +#include +#include +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +//################################################################################## +// Collect nodes and data from the AST for hook-insertion +class HookInsTargetFndr final : public VNVisitor { + AstNetlist* m_netlist + = nullptr; // Used for traversing AST from the beginning if the visitor is to deep + AstNodeModule* m_cellModp = nullptr; + AstModule* m_modp = nullptr; + AstModule* m_targetModp = nullptr; + bool m_error = false; + bool m_foundCellp = false; + bool m_foundModp = false; + bool m_foundVarp = false; + bool m_initModp = true; // If the visitor is in the first module node of the netlist + size_t m_insIdx = 0; + string m_currHier; + string m_target; + + // 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; + } + 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; + } + bool hasParam(AstModule* modp) { + for (AstNode* n = modp->op2p(); n; n = n->nextp()) { + if (n->name() == "HOOKINS") { return true; } + } + return false; + } + bool hasPin(AstCell* cellp) { + for (AstNode* n = cellp->paramsp(); n; n = n->nextp()) { + if (n->name() == "HOOKINS") { return true; } + } + return false; + } + bool hasMultiple(const std::string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { return it->second.multipleCellps; } + return false; + } + // Check if the direct predecessor in the target string has been hook-inserted, + // to create the correct link between the already hook-inserted module and the current one. + bool hasPrior(AstModule* modulep, const string& target) { + const auto& insCfg = V3Control::getHookInsCfg(); + auto priorTarget = reduce2Depth(split(target), KeyDepth::RelevantModule); + auto it = insCfg.find(priorTarget); + return it != insCfg.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); + } + bool targetHasPrefix(const string& prefix, const string& target) { + return cmpPrefix(prefix, target); + } + // Split given 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); + } + // Reduce 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; + } + } + void addParam(AstModule* modp) { + AstVar* paramp = new AstVar{modp->fileline(), VVarType::GPARAM, "HOOKINS", + VFlagChildDType{}, nullptr}; + paramp->valuep(new AstConst{modp->fileline(), AstConst::String{}, ""}); + paramp->dtypep(paramp->valuep()->dtypep()); + paramp->ansi(true); + modp->addStmtsp(paramp); + } + void addPin(AstCell* cellp, bool isInsPath, const string& target) { + int pinnum = 0; + if (isInsPath) { + for (AstNode* n = cellp->pinsp(); n; n = n->nextp()) { pinnum++; } + AstPin* pinp = new AstPin{cellp->fileline(), pinnum + 1, "HOOKINS", + // The pin is set to 1 to enable the hook-insertion path + new AstConst{cellp->fileline(), AstConst::String{}, target}}; + 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, "HOOKINS", + new AstParseRef{cellp->fileline(), "HOOKINS"}}; + pinp->param(true); + cellp->addParamsp(pinp); + } + } + void editInsData(AstCell* cellp, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.cellp = cellp; } + } + void editInsData(AstModule* modulep, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.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); + } + } + void setCell(AstCell* cellp, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.cellp = cellp; } + } + void setInsModule(AstModule* origModulep, AstModule* insModulep, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { + it->second.origModulep = origModulep; + it->second.insModulep = insModulep; + } + } + void setMultiple(const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.multipleCellps = true; } + } + void setPointingMod(AstModule* modulep, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.pointingModulep = modulep; } + } + void setProcessed(const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.processed = true; } + } + void setTopMod(AstModule* modulep, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { it->second.topModulep = modulep; } + } + void setVar(AstVar* varp, AstVar* insVarp, const string& target) { + auto& insCfg = V3Control::getHookInsCfg(); + auto it = insCfg.find(target); + if (it != insCfg.end()) { + for (auto& entry : it->second.entries) { + if (entry.varTarget == varp->name()) { + entry.origVarps = varp; + entry.insVarps = insVarp; + entry.found = true; + return; + } + } + } + } + + // VISITORS + void visit(AstModule* nodep) override { + if (m_initModp) { + if (targetHasTop(nodep->name(), m_target)) { + // Add decision parameters to the module if not present + 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 + } + // Store top module pointer for later + setTopMod(nodep, m_target); + iterateChildren(nodep); // Continue to Cell/Var nodes + } 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 // Find module pointed to by the cell from cell visitor + && (nodep = findModp(m_netlist, VN_CAST(m_cellModp, Module))) != nullptr) { + if (targetHasFullName(m_currHier, m_target)) { + AstModule* insModp = nullptr; + m_foundModp = true; + m_targetModp = nodep; + m_cellModp = nullptr; + // Check for prior changes made to the tree + if (hasPrior(nodep, m_currHier)) { + auto& insCfg = V3Control::getHookInsCfg(); + insModp + = insCfg.find(reduce2Depth(split(m_currHier), KeyDepth::RelevantModule)) + ->second.insModulep; + editInsData(insModp, m_currHier); + AstCell* cellp = nullptr; + for (AstNode* n = insModp->op2p(); n; n = n->nextp()) { + if (VN_IS(n, Cell) && (VN_CAST(n, Cell)->modp() == nodep) + && insCfg.find(m_currHier)->second.cellp->name() == n->name()) { + cellp = VN_CAST(n, Cell); + break; + } + } + editInsData(cellp, m_currHier); + } + if (!hasParam(nodep)) { addParam(nodep); } + insModp = nodep->cloneTree(false); + insModp->name(nodep->name() + "__hookIns__" + std::to_string(m_insIdx)); + if (hasMultiple(m_target)) { insModp->inLibrary(true); } + setInsModule(nodep, insModp, m_target); + iterateChildren(nodep); // Continue to var node + } 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); // Continue to cell + } 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); // Continue to cell + } + } 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 << "'"); + } + } + + void visit(AstCell* nodep) override { + 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, m_target); } + multCellForModp(nodep); + setCell(nodep->cloneTree(false, false), 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, m_target); } + multCellForModp(nodep); + setCell(nodep->cloneTree(false, false), 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, m_target); } + multCellForModp(nodep); + setCell(nodep->cloneTree(false, false), 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, m_target); } + multCellForModp(nodep); + } + } + + void visit(AstVar* nodep) override { + if (m_targetModp != nullptr) { + const HookInsertTarget& target = V3Control::getHookInsCfg().find(m_currHier)->second; + for (const auto& entry : target.entries) { + // Go over all var targets if in same module + if (nodep->name() == entry.varTarget) { + int width = 0; + // Check for if target var is supported + AstBasicDType* basicp = nodep->basicp(); + bool literal = basicp->isLiteralType(); + bool implicit = basicp->implicit(); + if (!implicit && nodep->basicp()->rangep() != nullptr) { + // Since the basicp is not implicit and there is a rangep, we can use the + // rangep for deducting 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->hasHookInserted(true); + 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() + "__hookIns__" + std::to_string(m_insIdx)); + setInsModule(m_modp, modulep, m_currHier); + m_initModp = false; + } + m_foundVarp = true; + } else if (nodep->nextp() == nullptr && !entry.found) { + v3error("Verilator-configfile': could not find defined 'var' in " + "'topModule.instance.var' ... Target string: '" + << m_target + "." + entry.varTarget << "'"); + return; + } + } + } + }; + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTOR + //------------------------------------------------------------------------------- + explicit HookInsTargetFndr(AstNetlist* nodep) { + const auto& insCfg = V3Control::getHookInsCfg(); + for (const auto& pair : insCfg) { + // Set initial flag values + m_netlist = nodep; + m_target = pair.first; + m_initModp = true; + m_currHier = ""; + iterate(nodep); + setProcessed(m_target); + // Reset flags + m_foundModp = false; + m_foundCellp = false; + m_foundVarp = false; + m_error = false; + m_targetModp = nullptr; + m_modp = nullptr; + m_insIdx++; + } + }; + ~HookInsTargetFndr() override = default; +}; + +//################################################################################## +// Do the hook-insertion transformations +class HookInsFunc final : public VNVisitor { + bool m_assignw = false; + bool m_assignNode = false; // Set to true to indicate that the visitor is in an assign + bool m_addedport = false; + bool m_addedTask = false; + bool m_addedFunc = false; + int m_pinnum = 0; + string m_targetKey; + string m_task_name; + size_t m_targetIndex = 0; + AstAlways* m_alwaysp = nullptr; + AstAssignW* m_assignwp = nullptr; + AstGenBlock* m_insGenBlock = nullptr; + AstTask* m_taskp = nullptr; + AstFunc* m_funcp = nullptr; + AstFuncRef* m_funcrefp = nullptr; + AstLoop* m_loopp = nullptr; + AstTaskRef* m_taskrefp = nullptr; + AstModule* m_current_module = nullptr; + AstModule* m_current_module_cell_check + = nullptr; // Stores the module node(used by cell visitor) + AstVar* m_tmp_varp = nullptr; + AstVar* m_orig_varp = nullptr; + AstVar* m_orig_varp_insMod = nullptr; + AstVar* m_dpi_trigger = nullptr; // Trigger ensuring changing execution of the DPI function + AstPort* m_orig_portp = nullptr; + + // METHODS + const HookInsertTarget* getInsCfg(const std::string& key) { + const auto& map = V3Control::getHookInsCfg(); + auto insCfg = map.find(key); + if (insCfg != map.end()) { + return &insCfg->second; + } else { + return nullptr; + } + } + AstCell* getMapEntryCell(const std::string& key) { + if (auto cfg = getInsCfg(key)) { return cfg->cellp; } + return nullptr; + } + AstModule* getMapEntryInsModule(const std::string& key) { + if (auto cfg = getInsCfg(key)) { return cfg->insModulep; } + return nullptr; + } + AstModule* getMapEntryPointingModule(const std::string& key) { + if (auto cfg = getInsCfg(key)) { return cfg->pointingModulep; } + return nullptr; + } + AstVar* getMapEntryInsVar(const std::string& key, size_t index) { + if (auto cfg = getInsCfg(key)) { + const auto& entries = cfg->entries; + if (index < entries.size()) { return entries[index].insVarps; } + } + return nullptr; + } + AstVar* getMapEntryVar(const std::string& key, size_t index) { + if (auto cfg = getInsCfg(key)) { + const auto& entries = cfg->entries; + if (index < entries.size()) { return entries[index].origVarps; } + } + return nullptr; + } + bool isTarget(const std::string& key) { + const auto& map = V3Control::getHookInsCfg(); + const auto insCfg = map.find(key); + return insCfg != map.end(); + } + bool isInsModEntry(AstModule* nodep, const std::string& key) { + const auto& map = V3Control::getHookInsCfg(); + const auto insCfg = map.find(key); + if (insCfg != map.end() && insCfg->second.insModulep == nodep) { + return true; + } else { + return false; + } + } + bool isTopModEntry(AstModule* nodep) { + auto& insCfg = V3Control::getHookInsCfg(); + for (const auto& pair : insCfg) { + if (nodep == pair.second.topModulep) { return true; } + } + return false; + } + bool isPointingModEntry(AstModule* nodep) { + auto& insCfg = V3Control::getHookInsCfg(); + for (const auto& pair : insCfg) { + if (nodep == pair.second.pointingModulep) { return true; } + } + return false; + } + bool isDone(AstModule* nodep) { + auto& insCfg = V3Control::getHookInsCfg(); + for (const auto& pair : insCfg) { + if (nodep == pair.second.insModulep) { return pair.second.done; } + } + return true; + } + bool hasMultiple(const std::string& key) { + const auto& map = V3Control::getHookInsCfg(); + const auto insCfg = map.find(key); + if (insCfg != map.end()) { + return insCfg->second.multipleCellps; + } else { + return false; + } + } + // Check if issues happend during collection in HookInsTargetFndr + bool hasNullptr(const std::pair& pair) { + bool moduleNullptr = pair.second.origModulep == nullptr; + bool cellNullptr = pair.second.cellp == nullptr; + return moduleNullptr || cellNullptr; + } + bool isFound(const std::pair& pair) { + for (auto& entry : pair.second.entries) { + if (entry.found == false) { return entry.found; } + } + return true; + } + int getMapEntryFaultCase(const std::string& key, size_t index) { + const auto& map = V3Control::getHookInsCfg(); + const auto insCfg = map.find(key); + if (insCfg != map.end()) { + const auto& entries = insCfg->second.entries; + if (index < entries.size()) { return entries[index].insID; } + return -1; // Return -1 if index is out of bounds + } else { + return -1; + } + } + string getMapEntryFunction(const std::string& key, size_t index) { + const auto& map = V3Control::getHookInsCfg(); + const auto insCfg = map.find(key); + if (insCfg != map.end()) { + const auto& entries = insCfg->second.entries; + if (index < entries.size()) { return entries[index].insFunc; } + return ""; + } else { + return ""; + } + } + // Remove "" from string from ->name() + string cleanString(const string& str) { + if (str.size() >= 2 && str.front() == '"' && str.back() == '"') { + return str.substr(1, str.size() - 2); + } else { + return ""; + } + } + void setDone(AstModule* nodep) { + auto& insCfg = V3Control::getHookInsCfg(); + for (auto& pair : insCfg) { + if (nodep == pair.second.insModulep) { pair.second.done = true; } + } + } + void insAssigns(AstNodeAssign* nodep) { + if (m_current_module != nullptr && m_orig_varp != nullptr && m_assignwp != nodep) { + m_assignNode = true; + VDirection dir = m_orig_varp->direction(); + if (dir == VDirection::INPUT || dir == VDirection::NONE) { + AstNodeExpr* rhsp = nodep->rhsp(); + if (rhsp->type() != VNType::ParseRef) { + for (AstNode* n = rhsp->op1p(); n; n = n->nextp()) { + if (n->type() == VNType::ParseRef && n->name() == m_orig_varp->name()) { + n->name(m_tmp_varp->name()); + break; + } + } + } else { + if (rhsp->name() == m_orig_varp->name()) { rhsp->name(m_tmp_varp->name()); } + } + } + } else if (nodep == m_assignwp) { + iterateChildren(nodep); + } + m_assignNode = false; + } + AstNode* createDPIInterface(AstModule* nodep, AstVar* orig_varp, const string& task_name) { + AstVar* varp = nullptr; + if (orig_varp->basicp()->isLiteralType() || orig_varp->basicp()->implicit()) { + int width = 0; + if (!orig_varp->basicp()->implicit() && orig_varp->basicp()->rangep() != nullptr) { + width = orig_varp->basicp()->rangep()->elementsConst(); + } else { + // Since Var is implicit set/assume the width as 1 like in V3Width.cpp in the + // AstVar visitor + width = 1; + } + if (width <= 1) { + varp = new AstVar{nodep->fileline(), VVarType::VAR, task_name, VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::BIT}}; + varp->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + varp->funcReturn(true); + varp->direction(VDirection::OUTPUT); + } else if (width <= 8) { + varp = new AstVar{nodep->fileline(), VVarType::VAR, task_name, VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::BYTE}}; + varp->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + varp->funcReturn(true); + varp->direction(VDirection::OUTPUT); + } else if (width <= 16) { + varp = new AstVar{nodep->fileline(), VVarType::VAR, task_name, VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::SHORTINT}}; + varp->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + varp->funcReturn(true); + varp->direction(VDirection::OUTPUT); + } else if (width <= 32) { + varp = new AstVar{nodep->fileline(), VVarType::VAR, task_name, VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT}}; + varp->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + varp->funcReturn(true); + varp->direction(VDirection::OUTPUT); + } else if (width <= 64) { + varp = new AstVar{nodep->fileline(), VVarType::VAR, task_name, VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::LONGINT}}; + varp->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + varp->funcReturn(true); + varp->direction(VDirection::OUTPUT); + } + return new AstFunc{nodep->fileline(), m_task_name, nullptr, varp}; + } else { + return new AstTask{nodep->fileline(), m_task_name, nullptr}; + } + } + + // Visitors + void visit(AstNetlist* nodep) override { + const auto& insCfg = V3Control::getHookInsCfg(); + for (const auto& pair : insCfg) { + if (hasNullptr(pair) || !isFound(pair)) { + v3error( + "Verilator-configfile: Incomplete hook-insertion configuration for target '" + << pair.first + << "'. Please check previous Errors from V3Instrument:findTargets and ensure" + << " all necessary components are correct defined."); + } else { + nodep->addModulesp(pair.second.insModulep); + m_targetKey = pair.first; + iterateChildren(nodep); + m_assignw = false; + } + } + } + void visit(AstModule* nodep) override { + const auto& insCfg = V3Control::getHookInsCfg().find(m_targetKey); + const HookInsertTarget& target = insCfg->second; + const auto& entries = target.entries; + // Insert nodes for hooks into module for each defined target + for (m_targetIndex = 0; m_targetIndex < entries.size(); ++m_targetIndex) { + m_tmp_varp = getMapEntryInsVar(m_targetKey, m_targetIndex); + m_orig_varp = getMapEntryVar(m_targetKey, m_targetIndex); + m_task_name = getMapEntryFunction(m_targetKey, m_targetIndex); + if (isInsModEntry(nodep, m_targetKey) && !isDone(nodep)) { + m_current_module = nodep; + // Add DPI function/task if not already present + 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); + m_funcp->verilogFunction(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); + } + } + // Prepare and add faulty variable + 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); + // Add trigger if not already present + if (m_dpi_trigger == nullptr) { + m_dpi_trigger = new AstVar{ + nodep->fileline(), VVarType::VAR, "dpi_trigger", VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::BIT, + VSigning::NOSIGN}}; + m_dpi_trigger->trace(false); + nodep->addStmtsp(m_dpi_trigger); + m_loopp = new AstLoop{nodep->fileline()}; + AstInitial* initialp = new AstInitial{ + nodep->fileline(), new AstBegin{nodep->fileline(), "", m_loopp, false}}; + nodep->addStmtsp(initialp); + } + // Add taks/function ref depending on taks/func added & create always block + 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(), m_tmp_varp->name()}, + m_funcrefp}; + AstAlways* alwaysp = new AstAlways{nodep->fileline(), VAlwaysKwd::CONT_ASSIGN, + nullptr, m_assignwp}; + nodep->addStmtsp(alwaysp); + } + if (m_targetIndex == entries.size() - 1) { setDone(nodep); } + // Get pin number for cell edits + 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 ((isPointingModEntry(nodep) || isTopModEntry(nodep)) + && !hasMultiple(m_targetKey)) { + m_current_module_cell_check = nodep; + AstCell* insCellp = getMapEntryCell(m_targetKey); + for (AstNode* n = insCellp->pinsp(); n; n = n->nextp()) { m_pinnum++; } + iterateChildren(nodep); + } else if (isPointingModEntry(nodep) && hasMultiple(m_targetKey)) { + m_current_module_cell_check = nodep; + AstCell* insCellp = getMapEntryCell(m_targetKey)->cloneTree(false); + insCellp->modp(getMapEntryInsModule(m_targetKey)); + for (AstNode* n = insCellp->pinsp(); n; n = n->nextp()) { m_pinnum++; } + // Add logic for deciding between original and hook-inserted module + // depending on the value of HOOKINS parameter + bool addedInitGenIf = false; + bool breakOuter = false; + std::string condValue = ""; + m_insGenBlock = new AstGenBlock{nodep->fileline(), "", insCellp, false}; + for (AstNode* n = nodep->op2p(); n; n = n->nextp()) { + if (VN_IS(n, GenIf)) { + condValue = cleanString(VN_CAST(n, GenIf)->condp()->op2p()->name()); + if (condValue != "" && isTarget(condValue)) { addedInitGenIf = true; } + for (AstNode* m = n; VN_CAST(m, GenIf)->elsesp()->op2p(); + m = VN_CAST(m, GenIf)->elsesp()->op2p()) { + if (VN_IS(m, GenIf)) { + condValue + = cleanString(VN_CAST(m, GenIf)->condp()->op2p()->name()); + if (condValue == m_targetKey) { + breakOuter = true; + break; + } + if (VN_CAST(m, GenIf)->elsesp()->op2p()->type() != VNType::GenIf) { + AstGenIf* genifp = new AstGenIf{ + nodep->fileline(), + new AstEq{nodep->fileline(), + new AstParseRef{nodep->fileline(), "HOOKINS"}, + new AstConst{nodep->fileline(), + AstConst::String{}, m_targetKey}}, + m_insGenBlock, + new AstGenBlock{ + nodep->fileline(), "", + getMapEntryCell(m_targetKey)->cloneTree(false), + false}}; + VN_CAST(m, GenIf)->elsesp()->op2p()->replaceWith(genifp); + breakOuter = true; + break; + } + } else { + v3fatal("Something went wrong!"); + } + } + } + if (breakOuter) { break; } + } + // If no GenIf for HOOKINS added yet, add one + if (!addedInitGenIf) { + AstGenIf* genifp = new AstGenIf{ + nodep->fileline(), + new AstEq{ + nodep->fileline(), new AstParseRef{nodep->fileline(), "HOOKINS"}, + new AstConst{nodep->fileline(), AstConst::String{}, m_targetKey}}, + m_insGenBlock, + new AstGenBlock{nodep->fileline(), "", + getMapEntryCell(m_targetKey)->cloneTree(false), false}}; + nodep->addStmtsp(genifp); + } + iterateChildren(m_insGenBlock); + 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_addedFunc = false; + m_addedport = false; + m_insGenBlock = nullptr; + } + m_dpi_trigger = nullptr; + m_loopp = nullptr; + m_targetIndex = 0; + } + void visit(AstPort* nodep) override { + // Replace original port with tmp port; keep original port for to be sure + 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; + } + } + void visit(AstCell* nodep) override { + bool nodeHasName = false; + bool nodeHasCorrectBackp = false; + bool isCorrectMultCell = false; + nodeHasName = (nodep->name() == getMapEntryCell(m_targetKey)->name()); + nodeHasCorrectBackp = (nodep->backp()->type() != VNType::GenBlock); + isCorrectMultCell = nodeHasName && nodeHasCorrectBackp; + // Edit cell reference depending on situation + if (m_current_module_cell_check != nullptr && !hasMultiple(m_targetKey) && nodeHasName) { + // Not multiple cells refer to target module; edit module reference + nodep->modp(getMapEntryInsModule(m_targetKey)); + if (m_orig_varp->direction() == VDirection::OUTPUT) { iterateChildren(nodep); } + } else if (m_current_module_cell_check != nullptr && hasMultiple(m_targetKey) + && isCorrectMultCell) { + // Multiple cells link to target module; + // delete original cell add logic with module visitor + nodep->unlinkFrBack(); + nodep->deleteTree(); + } else if (m_insGenBlock != nullptr && nodep->modp() == getMapEntryInsModule(m_targetKey) + && m_orig_varp->direction() == VDirection::OUTPUT) { + iterateChildren(nodep); + } else if (m_current_module != nullptr && m_orig_varp->direction() == VDirection::INPUT) { + iterateChildren(nodep); + } + } + void visit(AstPin* nodep) override { + 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()); + } + } + void visit(AstTask* nodep) override { + if (m_addedTask == false && nodep == m_taskp && m_current_module != nullptr) { + AstVar* insID = nullptr; + AstVar* var_x_task = nullptr; + AstVar* tmp_var_task = nullptr; + + insID = new AstVar{nodep->fileline(), VVarType::PORT, "insID", VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT, + VSigning::SIGNED, 32, 0}}; + insID->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(insID); + nodep->addStmtsp(var_x_task); + nodep->addStmtsp(tmp_var_task); + } + } + void visit(AstFunc* nodep) override { + if (m_addedFunc == false && nodep == m_funcp && m_current_module != nullptr) { + AstVar* insID = nullptr; + AstVar* dpi_trigger = nullptr; + AstVar* var_x_func = nullptr; + + insID = new AstVar{nodep->fileline(), VVarType::PORT, "insID", VFlagChildDType{}, + new AstBasicDType{nodep->fileline(), VBasicDTypeKwd::INT, + VSigning::SIGNED, 32, 0}}; + insID->direction(VDirection::INPUT); + + var_x_func = m_orig_varp->cloneTree(false); + var_x_func->varType(VVarType::PORT); + var_x_func->direction(VDirection::INPUT); + dpi_trigger = m_dpi_trigger->cloneTree(false); + dpi_trigger->varType(VVarType::PORT); + dpi_trigger->direction(VDirection::INPUT); + + nodep->addStmtsp(insID); + nodep->addStmtsp(dpi_trigger); + nodep->addStmtsp(var_x_func); + } + } + void visit(AstLoop* nodep) override { + // Add initial block for DPI trigger variable + if (nodep == m_loopp && m_current_module != nullptr) { + AstParseRef* initialParseRefrhsp + = new AstParseRef{nodep->fileline(), m_dpi_trigger->name()}; + AstParseRef* initialParseReflhsp + = new AstParseRef{nodep->fileline(), m_dpi_trigger->name()}; + AstBegin* initialBeginp = new AstBegin{ + nodep->fileline(), "", + new AstAssign{nodep->fileline(), initialParseReflhsp, + new AstLogNot{nodep->fileline(), initialParseRefrhsp}}, + false}; + initialBeginp->addStmtsp( + new AstDelay{nodep->fileline(), + new AstConst{nodep->fileline(), AstConst::Unsized32{}, 1}, false}); + nodep->addContsp(initialBeginp); + } + } + void visit(AstAlways* nodep) override { + // Add task reference in the new always block + 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) override { + // Store hooked var in hooked module to ensure correct references + if (m_current_module != nullptr && nodep->name() == m_orig_varp->name()) { + m_orig_varp_insMod = nodep; + } + } + void visit(AstTaskRef* nodep) override { + if (nodep == m_taskrefp && m_current_module != nullptr) { + AstConst* constp_id = nullptr; + constp_id = new AstConst{ + nodep->fileline(), AstConst::Unsized32{}, + static_cast(getMapEntryFaultCase(m_targetKey, m_targetIndex))}; + + AstVarRef* added_varrefp + = new AstVarRef{nodep->fileline(), m_orig_varp_insMod, 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(), m_tmp_varp->name()}}); + m_orig_varp_insMod = nullptr; + } + } + void visit(AstFuncRef* nodep) override { + if (nodep == m_funcrefp && m_current_module != nullptr) { + AstConst* constp_id = nullptr; + + constp_id = new AstConst{ + nodep->fileline(), AstConst::Unsized32{}, + static_cast(getMapEntryFaultCase(m_targetKey, m_targetIndex))}; + + AstVarRef* added_triggerp + = new AstVarRef{nodep->fileline(), m_dpi_trigger, VAccess::READ}; + + AstVarRef* added_varrefp + = new AstVarRef{nodep->fileline(), m_orig_varp_insMod, VAccess::READ}; + + nodep->addPinsp(new AstArg{nodep->fileline(), "", constp_id}); + nodep->addPinsp(new AstArg{nodep->fileline(), "", added_triggerp}); + nodep->addPinsp(new AstArg{nodep->fileline(), "", added_varrefp}); + m_orig_varp_insMod = nullptr; + m_funcrefp = nullptr; + } + } + void visit(AstAssignW* nodep) override { insAssigns(nodep); } // Edit assigns if needed + void visit(AstAssign* nodep) override { insAssigns(nodep); } // Edit assigns if needed + void visit(AstAssignDly* nodep) override { insAssigns(nodep); } // Edit assigns if needed + void visit(AstAssignForce* nodep) override { insAssigns(nodep); } // Edit assigns if needed + void visit(AstParseRef* nodep) override { + // Replace original var with tmp var in non-assign nodes + if (m_current_module != nullptr && m_orig_varp != nullptr + && m_orig_varp->direction() != VDirection::OUTPUT) { + if (nodep->name() == m_orig_varp->name() && !m_assignNode) { + nodep->name(m_tmp_varp->name()); + } + } + } + + //----------------- + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit HookInsFunc(AstNetlist* nodep) { iterate(nodep); } + ~HookInsFunc() override = default; +}; + +//################################################################################## +// Hook-insertion class functions + +void V3InsertHook::findTargets(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { HookInsTargetFndr{nodep}; } + V3Global::dumpCheckGlobalTree("hookInsertFinder", 0, dumpTreeEitherLevel() >= 3); +} + +void V3InsertHook::insertHooks(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { HookInsFunc{nodep}; } + V3Global::dumpCheckGlobalTree("hookInsertFunction", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3InsertHook.h b/src/V3InsertHook.h new file mode 100644 index 000000000..6d7e1e628 --- /dev/null +++ b/src/V3InsertHook.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_V3INSERTHOOK_H_ +#define VERILATOR_V3INSERTHOOK_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//========================================================================= + +class V3InsertHook final { +public: + static void findTargets(AstNetlist* nodep) VL_MT_DISABLED; + static void insertHooks(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 306bced64..a3110f0d6 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1985,6 +1985,8 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, addIncDirUser(parseFileArg(optdir, string{valp})); }).notForRerun(); + DECL_OPTION("-insert-hook", OnOff, &m_insertHook); + parser.finalize(); const std::string cwd = V3Os::filenameRealPath("."); diff --git a/src/V3Options.h b/src/V3Options.h index 1af02e49c..3316025be 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -309,6 +309,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_insertHook = false; // main switch: --insert-hook int m_buildJobs = -1; // main switch: --build-jobs, -j int m_coverageExprMax = 32; // main switch: --coverage-expr-max @@ -586,6 +587,7 @@ public: bool xmlOnly() const { return m_xmlOnly; } bool serializeOnly() const { return m_xmlOnly || m_jsonOnly; } bool topIfacesSupported() const { return lintOnly() && !hierarchical(); } + bool insertHook() const { return m_insertHook; } 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 363a50b86..a3793fd02 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -63,6 +63,7 @@ #include "V3Graph.h" #include "V3HierBlock.h" #include "V3Inline.h" +#include "V3InsertHook.h" #include "V3Inst.h" #include "V3Interface.h" #include "V3LibMap.h" @@ -154,6 +155,13 @@ static void process() { v3Global.vlExit(0); } + // Hook-insert design with the configurations given in .vlt file + if (v3Global.opt.insertHook()) { + v3Global.dpi(true); + V3InsertHook::findTargets(v3Global.rootp()); + V3InsertHook::insertHooks(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 d7f62b0c3..cdc83bee6 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -141,6 +141,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; } + "insert_hook" { FL; return yVLT_INSERTHOOK; } "isolate_assignments" { FL; return yVLT_ISOLATE_ASSIGNMENTS; } "lint_off" { FL; return yVLT_LINT_OFF; } "lint_on" { FL; return yVLT_LINT_ON; } @@ -163,11 +164,13 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "tracing_on" { FL; return yVLT_TRACING_ON; } -?"-block" { FL; return yVLT_D_BLOCK; } + -?"-callback" { FL; return yVLT_D_CALLBACK; } -?"-contents" { FL; return yVLT_D_CONTENTS; } -?"-cost" { FL; return yVLT_D_COST; } -?"-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; } -?"-levels" { FL; return yVLT_D_LEVELS; } -?"-lines" { FL; return yVLT_D_LINES; } -?"-match" { FL; return yVLT_D_MATCH; } @@ -178,6 +181,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} -?"-port" { FL; return yVLT_D_PORT; } -?"-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 078ddd850..859c55d96 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_INSERTHOOK "insert_hook" %token yVLT_ISOLATE_ASSIGNMENTS "isolate_assignments" %token yVLT_LINT_OFF "lint_off" %token yVLT_LINT_ON "lint_on" @@ -272,11 +273,13 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_TRACING_ON "tracing_on" %token yVLT_D_BLOCK "--block" +%token yVLT_D_CALLBACK "--callback" %token yVLT_D_CONTENTS "--contents" %token yVLT_D_COST "--cost" %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_LEVELS "--levels" %token yVLT_D_LINES "--lines" %token yVLT_D_MATCH "--match" @@ -287,6 +290,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_D_PORT "--port" %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" @@ -8095,6 +8099,8 @@ vltItem: { /* Historical, now has no effect */ } | vltInlineFront vltDModuleE vltDFTaskE { V3Control::addInline($1, *$2, *$3, $1); } + | yVLT_INSERTHOOK yVLT_D_CALLBACK yaSTRING yVLT_D_ID yaINTNUM yVLT_D_TARGET yaSTRING + { V3Control::addHookInsCfg($1, *$3, $5->toSInt(), *$7); } | yVLT_COVERAGE_BLOCK_OFF vltDFile { V3Control::addCoverageBlockOff(*$2, 0); } | yVLT_COVERAGE_BLOCK_OFF vltDFile yVLT_D_LINES yaINTNUM diff --git a/test_regress/t/t_instrument.cpp b/test_regress/t/t_instrument.cpp new file mode 100644 index 000000000..ef8af0cf7 --- /dev/null +++ b/test_regress/t/t_instrument.cpp @@ -0,0 +1,51 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +#include +#include + +#include + +#include VM_PREFIX_INCLUDE + +vluint64_t main_time = 0; +double sc_time_stamp() { return main_time; } + +int main(int argc, char** argv) { + Verilated::debug(0); + Verilated::commandArgs(argc, argv); + + const std::unique_ptr top{new VM_PREFIX{"TOP"}}; + + while (main_time <= 100) { + if (main_time < 20) { + top->in1a = 5; + top->in2a = 10; + top->in1b = 20; + top->in2b = 30; + } else if (main_time >= 20 && main_time < 63) { + top->in1a = 0; + top->in2a = 5; + top->in1b = 15; + top->in2b = 25; + } else if (main_time > 78) { + top->in1a = 10; + top->in2a = 15; + top->in1b = 25; + top->in2b = 35; + } + top->eval(); + std::cout << "$time: " << main_time << " | " + << "Output outa: " << static_cast(top->outa) << " | " + << "Output outb: " << static_cast(top->outb) << std::endl; + ++main_time; + } + top->final(); + printf("*-* All Finished *-*\n"); + return 0; +} diff --git a/test_regress/t/t_instrument.out b/test_regress/t/t_instrument.out new file mode 100644 index 000000000..7f87814a2 --- /dev/null +++ b/test_regress/t/t_instrument.out @@ -0,0 +1,102 @@ +$time: 0 | Output outa: 15 | Output outb: 1 +$time: 1 | Output outa: 15 | Output outb: 1 +$time: 2 | Output outa: 15 | Output outb: 1 +$time: 3 | Output outa: 15 | Output outb: 50 +$time: 4 | Output outa: 15 | Output outb: 50 +$time: 5 | Output outa: 15 | Output outb: 50 +$time: 6 | Output outa: 15 | Output outb: 50 +$time: 7 | Output outa: 15 | Output outb: 50 +$time: 8 | Output outa: 15 | Output outb: 50 +$time: 9 | Output outa: 15 | Output outb: 50 +$time: 10 | Output outa: 0 | Output outb: 50 +$time: 11 | Output outa: 0 | Output outb: 50 +$time: 12 | Output outa: 0 | Output outb: 50 +$time: 13 | Output outa: 0 | Output outb: 50 +$time: 14 | Output outa: 0 | Output outb: 50 +$time: 15 | Output outa: 0 | Output outb: 50 +$time: 16 | Output outa: 0 | Output outb: 50 +$time: 17 | Output outa: 0 | Output outb: 50 +$time: 18 | Output outa: 0 | Output outb: 50 +$time: 19 | Output outa: 0 | Output outb: 50 +$time: 20 | Output outa: 5 | Output outb: 40 +$time: 21 | Output outa: 5 | Output outb: 40 +$time: 22 | Output outa: 5 | Output outb: 40 +$time: 23 | Output outa: 5 | Output outb: 40 +$time: 24 | Output outa: 5 | Output outb: 40 +$time: 25 | Output outa: 5 | Output outb: 40 +$time: 26 | Output outa: 5 | Output outb: 40 +$time: 27 | Output outa: 5 | Output outb: 40 +$time: 28 | Output outa: 5 | Output outb: 40 +$time: 29 | Output outa: 5 | Output outb: 40 +$time: 30 | Output outa: 5 | Output outb: 40 +$time: 31 | Output outa: 5 | Output outb: 40 +$time: 32 | Output outa: 5 | Output outb: 1 +$time: 33 | Output outa: 5 | Output outb: 1 +$time: 34 | Output outa: 5 | Output outb: 1 +$time: 35 | Output outa: 5 | Output outb: 1 +$time: 36 | Output outa: 5 | Output outb: 1 +$time: 37 | Output outa: 5 | Output outb: 1 +$time: 38 | Output outa: 5 | Output outb: 1 +$time: 39 | Output outa: 5 | Output outb: 1 +$time: 40 | Output outa: 5 | Output outb: 1 +$time: 41 | Output outa: 5 | Output outb: 1 +$time: 42 | Output outa: 5 | Output outb: 1 +$time: 43 | Output outa: 5 | Output outb: 1 +$time: 44 | Output outa: 5 | Output outb: 1 +$time: 45 | Output outa: 5 | Output outb: 1 +$time: 46 | Output outa: 5 | Output outb: 1 +$time: 47 | Output outa: 5 | Output outb: 1 +$time: 48 | Output outa: 5 | Output outb: 1 +$time: 49 | Output outa: 5 | Output outb: 1 +$time: 50 | Output outa: 5 | Output outb: 1 +$time: 51 | Output outa: 5 | Output outb: 1 +$time: 52 | Output outa: 5 | Output outb: 1 +$time: 53 | Output outa: 5 | Output outb: 1 +$time: 54 | Output outa: 5 | Output outb: 1 +$time: 55 | Output outa: 5 | Output outb: 1 +$time: 56 | Output outa: 5 | Output outb: 1 +$time: 57 | Output outa: 5 | Output outb: 1 +$time: 58 | Output outa: 5 | Output outb: 1 +$time: 59 | Output outa: 5 | Output outb: 1 +$time: 60 | Output outa: 5 | Output outb: 1 +$time: 61 | Output outa: 5 | Output outb: 1 +$time: 62 | Output outa: 5 | Output outb: 1 +$time: 63 | Output outa: 5 | Output outb: 1 +$time: 64 | Output outa: 5 | Output outb: 1 +$time: 65 | Output outa: 5 | Output outb: 1 +$time: 66 | Output outa: 5 | Output outb: 1 +$time: 67 | Output outa: 5 | Output outb: 1 +$time: 68 | Output outa: 5 | Output outb: 1 +$time: 69 | Output outa: 5 | Output outb: 40 +$time: 70 | Output outa: 5 | Output outb: 40 +$time: 71 | Output outa: 5 | Output outb: 40 +$time: 72 | Output outa: 5 | Output outb: 40 +$time: 73 | Output outa: 5 | Output outb: 40 +$time: 74 | Output outa: 5 | Output outb: 40 +$time: 75 | Output outa: 5 | Output outb: 40 +$time: 76 | Output outa: 5 | Output outb: 40 +$time: 77 | Output outa: 5 | Output outb: 40 +$time: 78 | Output outa: 5 | Output outb: 40 +$time: 79 | Output outa: 25 | Output outb: 60 +$time: 80 | Output outa: 25 | Output outb: 60 +$time: 81 | Output outa: 25 | Output outb: 60 +$time: 82 | Output outa: 25 | Output outb: 60 +$time: 83 | Output outa: 25 | Output outb: 60 +$time: 84 | Output outa: 25 | Output outb: 60 +$time: 85 | Output outa: 0 | Output outb: 60 +$time: 86 | Output outa: 0 | Output outb: 60 +$time: 87 | Output outa: 0 | Output outb: 60 +$time: 88 | Output outa: 0 | Output outb: 60 +$time: 89 | Output outa: 0 | Output outb: 60 +$time: 90 | Output outa: 0 | Output outb: 60 +$time: 91 | Output outa: 0 | Output outb: 60 +$time: 92 | Output outa: 0 | Output outb: 60 +$time: 93 | Output outa: 0 | Output outb: 60 +$time: 94 | Output outa: 0 | Output outb: 60 +$time: 95 | Output outa: 0 | Output outb: 60 +$time: 96 | Output outa: 0 | Output outb: 60 +$time: 97 | Output outa: 0 | Output outb: 60 +$time: 98 | Output outa: 0 | Output outb: 60 +$time: 99 | Output outa: 0 | Output outb: 60 +$time: 100 | Output outa: 0 | Output outb: 60 +*-* All Finished *-* diff --git a/test_regress/t/t_instrument.py b/test_regress/t/t_instrument.py new file mode 100755 index 000000000..34ef9eccc --- /dev/null +++ b/test_regress/t/t_instrument.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 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 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_instrument.v" + +sim_filename = "t/" + test.name + ".cpp" +dpi_filename = "t/t_instrumentDPI.cpp" +vlt_filename = "t/" + test.name + ".vlt" + +test.compile( + make_top_shell=False, + make_main=False, + v_flags2=["--trace --timing --exe --instrument", sim_filename, vlt_filename, dpi_filename]) +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_instrument.v b/test_regress/t/t_instrument.v new file mode 100644 index 000000000..71471aed9 --- /dev/null +++ b/test_regress/t/t_instrument.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2012 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module top_module( + input reg [7:0] in1a, + input reg [7:0] in2a, + input reg [7:0] in1b, + input reg [7:0] in2b, + output logic [7:0] outa, + output logic [7:0] outb +); + + module_a a1 (.in1(in1a), .in2(in2a), .out(outa)); + module_a a2 (.in1(in1b), .in2(in2b), .out(outb)); + +endmodule + +module module_a( + input logic [7:0] in1, + input logic [7:0] in2, + output logic [7:0] out +); + module_b b1 (.in1(in1), .in2(in2), .out(out)); +endmodule + +module module_b ( + input logic [7:0] in1, + input logic [7:0] in2, + output logic [7:0] out +); + reg [127:0] bigRegister; + always_comb begin + out = in1 + in2; + end +endmodule diff --git a/test_regress/t/t_instrument.vlt b/test_regress/t/t_instrument.vlt new file mode 100644 index 000000000..b0b8351f4 --- /dev/null +++ b/test_regress/t/t_instrument.vlt @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +instrument -callback "instrument_var" -id 0 -target "top_module.a1.b1.out" + +instrument -callback "instrument_var" -id 1 -target "top_module.a2.out" diff --git a/test_regress/t/t_instrumentDPI.cpp b/test_regress/t/t_instrumentDPI.cpp new file mode 100644 index 000000000..001f19717 --- /dev/null +++ b/test_regress/t/t_instrumentDPI.cpp @@ -0,0 +1,30 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include + +extern "C" int instrument_var(int id, int trigger, const svLogic* x) { + switch (id) { + case 0: + if ((VL_TIME_Q() >= 10 && VL_TIME_Q() < 20) || VL_TIME_Q() >= 85) { + return 0; + } else { + return *x; + } + //return 0; + case 1: + if ((VL_TIME_Q() < 3) || (VL_TIME_Q() >= 32 && VL_TIME_Q() < 69)) { + return 1; + } else { + return *x; + } + default: return *x; + } +} diff --git a/test_regress/t/t_instrument_bad1.out b/test_regress/t/t_instrument_bad1.out new file mode 100644 index 000000000..e228e30a1 --- /dev/null +++ b/test_regress/t/t_instrument_bad1.out @@ -0,0 +1,2 @@ +%Error: In .vlt defined target tries to instrument the highest MODULE, is not possible! ... Target string: top_module.outa + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. diff --git a/test_regress/t/t_instrument_bad1.py b/test_regress/t/t_instrument_bad1.py new file mode 100755 index 000000000..9136599a6 --- /dev/null +++ b/test_regress/t/t_instrument_bad1.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 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 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_instrument.v" + +sim_filename = "t/t_instrument.cpp" +dpi_filename = "t/t_instrumentDPI.cpp" +vlt_filename = "t/" + test.name + ".vlt" + +test.compile( + fails=True, + make_top_shell=False, + make_main=False, + v_flags2=["--trace --timing --exe --instrument", sim_filename, vlt_filename, dpi_filename], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_instrument_bad1.vlt b/test_regress/t/t_instrument_bad1.vlt new file mode 100644 index 000000000..2a34c9bd0 --- /dev/null +++ b/test_regress/t/t_instrument_bad1.vlt @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +instrument -callback "instrument_var" -id 0 -target "top_module.outa" diff --git a/test_regress/t/t_instrument_bad2.out b/test_regress/t/t_instrument_bad2.out new file mode 100644 index 000000000..9b6c5cc9f --- /dev/null +++ b/test_regress/t/t_instrument_bad2.out @@ -0,0 +1,12 @@ +%Error: Verilator-configfile': could not find initial 'module' in 'module.instance.__' ... Target: 'top.a1.b1' + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Verilator-configfile: target variable 'bigRegister' in 'top_module.a1.b1' must be a supported type! +%Error: Verilator-configfile': could not find defined 'var' in 'topModule.instance.var' ... Target string: 'top_module.a1.b1.clk' +%Error: Verilator-configfile': could not find '.var' in '__.module.var' ... Target: 'top_module.a1.b1' +%Error: Verilator-configfile: could not find 'instance' in '__.instance.__' ... Target string: 'top_module.a1.b3' +%Error: Verilator-configfile': could not find initial 'instance' in 'topModule.instance.__' ... Target string: 'top_module.a3.b1' +%Error: Verilator-configfile: Incomplete instrumentation configuration for target 'top.a1.b1'. Please check previous Errors from V3Instrument:findTargets and ensure all necessary components are correct defined. +%Error: Verilator-configfile: Incomplete instrumentation configuration for target 'top_module.a1.b1'. Please check previous Errors from V3Instrument:findTargets and ensure all necessary components are correct defined. +%Error: Verilator-configfile: Incomplete instrumentation configuration for target 'top_module.a1.b3'. Please check previous Errors from V3Instrument:findTargets and ensure all necessary components are correct defined. +%Error: Verilator-configfile: Incomplete instrumentation configuration for target 'top_module.a3.b1'. Please check previous Errors from V3Instrument:findTargets and ensure all necessary components are correct defined. +%Error: Exiting due to diff --git a/test_regress/t/t_instrument_bad2.py b/test_regress/t/t_instrument_bad2.py new file mode 100755 index 000000000..9136599a6 --- /dev/null +++ b/test_regress/t/t_instrument_bad2.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 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 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_instrument.v" + +sim_filename = "t/t_instrument.cpp" +dpi_filename = "t/t_instrumentDPI.cpp" +vlt_filename = "t/" + test.name + ".vlt" + +test.compile( + fails=True, + make_top_shell=False, + make_main=False, + v_flags2=["--trace --timing --exe --instrument", sim_filename, vlt_filename, dpi_filename], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_instrument_bad2.vlt b/test_regress/t/t_instrument_bad2.vlt new file mode 100644 index 000000000..cad5d1bfe --- /dev/null +++ b/test_regress/t/t_instrument_bad2.vlt @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +instrument -callback "instrument_var" -id 0 -target "top.a1.b1.out" + +instrument -callback "instrument_var" -id 0 -target "top_module.a3.b1.out" + +instrument -callback "instrument_var" -id 0 -target "top_module.a1.b3.out" + +instrument -callback "instrument_var" -id 0 -target "top_module.a1.b1.clk" + +instrument -callback "instrument_var" -id 0 -target "top_module.a1.b1.bigRegister"