// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Hierarchical Verilation for large designs // // 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 // //************************************************************************* // Hierarchical Verilation is useful for large designs. // It reduces // - time and memory for Verilation // - compilation time especially when a hierarchical block is used many times // // Hierarchical Verilation internally uses --lib-create for each // hierarchical block. Upper modules read the wrapper from --lib-create // instead of the Verilog design. // // Hierarchical Verilation runs as the following step // 1) Find modules marked by /*verilator hier_block*/ metacomment // 2) Generate ${prefix}_hier.mk to create protected-lib for hierarchical blocks and // final Verilation to process the top module, that refers wrappers // 3) Call child Verilator process via ${prefix}_hier.mk // // There are 3 kinds of Verilator run. // a) To create ${prefix}_hier.mk (--hierarchical) // b) To --lib-create on each hierarchical block (--hierarchical-child) // c) To load wrappers and Verilate the top module (... what primary flags?) // // Then user can build Verilated module as usual. // // Here is more detailed internal process. // 1) Parser adds VPragmaType::HIER_BLOCK of AstPragma to modules // that are marked with /*verilator hier_block*/ metacomment in Verilator run a). // 2) If module type parameters are present, V3Control marks hier param modules // (marked with hier_params verilator config pragma) as modp->hierParams(true). // This is done in run b), de-parameterized modules are mapped with their params one-to-one. // 3) AstModule with HIER_BLOCK pragma is marked modp->hierBlock(true) // in V3LinkResolve.cpp during run a). // 4) In V3LinkCells.cpp, the following things are done during run b) and c). // 4-1) Delete the upper modules of the hierarchical block because the top module in run b) is // hierarchical block, not the top module of run c). // 4-2) If the top module of the run b) or c) instantiates other hierarchical blocks that is // parameterized, // module and task names are renamed to the original name to be compatible with the // hier module to be called. // // Parameterized modules have unique name by V3Param.cpp. The unique name contains '__' and // Verilator encodes '__' when loading such symbols. // 5) In V3LinkDot.cpp, // 5-1) Dotted access across hierarchical block boundary is checked. Currently hierarchical // block references are not supported. // 5-2) If present, parameters in hier params module replace parameter values of // de-parameterized module in run b). // 6) In V3Dead.cpp, some parameters of parameterized modules are protected not to be deleted even // if the parameter is not referred. This protection is necessary to match step 6) below. // 7) In V3Param.cpp, use --lib-create wrapper of the parameterized module made in b) and c). // If a hierarchical block is a parameterized module and instantiated in multiple locations, // all parameters must exactly match. // 8) In V3HierBlock.cpp, relationships among hierarchical blocks are checked in run a). // (which block uses other blocks..) // 9) In V3EmitMk.cpp, ${prefix}_hier.mk is created in run a). // // There are three hidden command options: // --hierarchical-child is added to Verilator run b). // --hierarchical-block module_name,mangled_name,name0,value0,name1,value1,... // module_name :The original modulename // mangled_name :Mangled name of parameterized modules (named in V3Param.cpp). // Same as module_name for non-parameterized hierarchical block. // name :The name of the parameter // value :Overridden value of the parameter // // Used for b) and c). // These options are repeated for all instantiated hierarchical blocks. // --hierarchical-params-file filename // filename :Name of a hierarchical parameters file // // Added in a), used for b). // Each de-parameterized module version has exactly one hier params file specified. #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3HierBlock.h" #include "V3Control.h" #include "V3EmitV.h" #include "V3File.h" #include "V3Os.h" #include "V3Stats.h" #include "V3String.h" #include #include #include #include VL_DEFINE_DEBUG_FUNCTIONS; static string V3HierCommandArgsFilename(const string& prefix, bool forMkJson) { return v3Global.opt.makeDir() + "/" + prefix + (forMkJson ? "__hierMkJsonArgs.f" : "__hierMkArgs.f"); } static string V3HierParametersFileName(const string& prefix) { return v3Global.opt.makeDir() + "/" + prefix + "__hierParameters.v"; } static void V3HierWriteCommonInputs(const V3HierBlock* hblockp, std::ostream* of, bool forMkJson) { string topModuleFile; if (hblockp) topModuleFile = hblockp->vFileIfNecessary(); if (!forMkJson) { if (!topModuleFile.empty()) *of << topModuleFile << "\n"; for (const auto& i : v3Global.opt.vFiles()) *of << i.filename() << "\n"; } for (const auto& i : v3Global.opt.libraryFiles()) { if (V3Os::filenameRealPath(i.filename()) != topModuleFile) *of << "-v " << i.filename() << "\n"; } } //###################################################################### V3HierBlock::StrGParams V3HierBlock::stringifyParams(const std::vector& gparams, bool forGOption) { StrGParams strParams; for (const AstVar* const gparam : gparams) { if (const AstConst* const constp = VN_CAST(gparam->valuep(), Const)) { string s; // Only constant parameter needs to be set to -G because already checked in // V3Param.cpp. See also ParamVisitor::checkSupportedParam() in the file. if (constp->isDouble()) { // 64 bit width of hex can be expressed with 16 chars. // 32 chars must be long enough for hexadecimal floating point // considering prefix of '0x', '.', and 'P'. std::vector hexFpStr(32, '\0'); const int len = VL_SNPRINTF(hexFpStr.data(), hexFpStr.size(), "%a", constp->num().toDouble()); UASSERT_OBJ(0 < len && static_cast(len) < hexFpStr.size(), constp, " is not properly converted to string"); s = hexFpStr.data(); } else if (constp->isString()) { s = constp->num().toString(); if (!forGOption) s = VString::quoteBackslash(s); s = VString::quoteStringLiteralForShell(s); } else { // Either signed or unsigned integer. s = constp->num().ascii(true, true); s = VString::quoteAny(s, '\'', '\\'); } strParams.emplace_back(gparam->name(), s); } } return strParams; } VStringList V3HierBlock::commandArgs(bool forMkJson) const { VStringList opts; const string prefix = hierPrefix(); if (!forMkJson) { opts.push_back(" --prefix " + prefix); opts.push_back(" --mod-prefix " + prefix); opts.push_back(" --top-module " + modp()->name()); } opts.push_back(" --lib-create " + modp()->name()); // possibly mangled name if (v3Global.opt.protectKeyProvided()) opts.push_back(" --protect-key " + v3Global.opt.protectKeyDefaulted()); opts.push_back(" --hierarchical-child " + cvtToStr(v3Global.opt.threads())); const StrGParams gparamsStr = stringifyParams(m_params, true); for (const StrGParam& param : gparamsStr) { opts.push_back("-G" + param.first + "=" + param.second + ""); } if (!m_typeParams.empty()) { opts.push_back(" --hierarchical-params-file " + typeParametersFilename()); } const int blockThreads = V3Control::getHierWorkers(m_modp->origName()); if (blockThreads > 1) { if (!inEmpty()) { V3Control::getHierWorkersFileLine(m_modp->origName()) ->v3warn(E_UNSUPPORTED, "Specifying workers for nested hierarchical blocks"); } else { if (v3Global.opt.threads() < blockThreads) { m_modp->v3error("Hierarchical blocks cannot be scheduled on more threads than in " "thread pool, threads = " << v3Global.opt.threads() << " hierarchical block threads = " << blockThreads); } opts.push_back(" --threads " + std::to_string(blockThreads)); } } return opts; } VStringList V3HierBlock::hierBlockArgs() const { VStringList opts; const StrGParams gparamsStr = stringifyParams(m_params, false); opts.push_back("--hierarchical-block "); string s = modp()->origName(); // origName s += "," + modp()->name(); // mangledName for (const StrGParam& pair : gparamsStr) { s += "," + pair.first; s += "," + pair.second; } opts.back() += s; return opts; } string V3HierBlock::hierPrefix() const { return "V" + modp()->name(); } string V3HierBlock::hierSomeFilename(bool withDir, const char* prefix, const char* suffix) const { string s; if (withDir) s = hierPrefix() + '/'; s += prefix + modp()->name() + suffix; return s; } string V3HierBlock::hierWrapperFilename(bool withDir) const { return hierSomeFilename(withDir, "", ".sv"); } string V3HierBlock::hierMkFilename(bool withDir) const { return hierSomeFilename(withDir, "V", ".mk"); } string V3HierBlock::hierLibFilename(bool withDir) const { return hierSomeFilename(withDir, "lib", ".a"); } string V3HierBlock::hierGeneratedFilenames(bool withDir) const { return hierWrapperFilename(withDir) + ' ' + hierMkFilename(withDir); } string V3HierBlock::vFileIfNecessary() const { string filename = V3Os::filenameRealPath(m_modp->fileline()->filename()); for (const auto& v : v3Global.opt.vFiles()) { // Already listed in vFiles, so no need to add the file. if (filename == V3Os::filenameRealPath(v.filename())) return ""; } return filename; } void V3HierBlock::writeCommandArgsFile(bool forMkJson) const { const std::unique_ptr of{V3File::new_ofstream(commandArgsFilename(forMkJson))}; *of << "--cc\n"; if (!forMkJson) { for (const V3GraphEdge& edge : outEdges()) { const V3HierBlock* const dependencyp = edge.top()->as(); *of << v3Global.opt.makeDir() << "/" << dependencyp->hierWrapperFilename(true) << "\n"; } *of << "-Mdir " << v3Global.opt.makeDir() << "/" << hierPrefix() << " \n"; } V3HierWriteCommonInputs(this, of.get(), forMkJson); const VStringList& commandOpts = commandArgs(false); for (const string& opt : commandOpts) *of << opt << "\n"; *of << hierBlockArgs().front() << "\n"; for (const V3GraphEdge& edge : outEdges()) { const V3HierBlock* const dependencyp = edge.top()->as(); *of << dependencyp->hierBlockArgs().front() << "\n"; } *of << v3Global.opt.allArgsStringForHierBlock(false) << "\n"; } string V3HierBlock::commandArgsFilename(bool forMkJson) const { return V3HierCommandArgsFilename(hierPrefix(), forMkJson); } string V3HierBlock::typeParametersFilename() const { return V3HierParametersFileName(hierPrefix()); } void V3HierBlock::writeParametersFile() const { if (m_typeParams.empty()) return; VHashSha256 hash{"type params"}; const string moduleName = "Vhsh" + hash.digestSymbol(); const std::unique_ptr of{V3File::new_ofstream(typeParametersFilename())}; *of << "module " << moduleName << ";\n"; for (AstParamTypeDType* const gparam : m_typeParams) { AstTypedef* tdefp = new AstTypedef{new FileLine{FileLine::builtInFilename()}, gparam->name(), nullptr, VFlagChildDType{}, gparam->skipRefp()->cloneTreePure(true)}; V3EmitV::verilogForTree(tdefp, *of); VL_DO_DANGLING(tdefp->deleteTree(), tdefp); } *of << "endmodule\n\n"; *of << "`verilator_config\n"; *of << "hier_params -module \"" << moduleName << "\"\n"; } //###################################################################### // Construct graph of hierarchical blocks class HierBlockUsageCollectVisitor final : public VNVisitorConst { // NODE STATE // AstNode::user1() -> bool. Already visited const VNUser1InUse m_inuser1; // STATE V3HierGraph* const m_graphp = new V3HierGraph{}; // The graph of hierarchical blocks // Map from hier blocks to the corresponding V3HierBlock graph vertex std::unordered_map m_mod2vtx; AstModule* m_modp = nullptr; // The current module std::vector m_params; // Overridden value parameters of current module std::vector m_typeParams; // Type parameters of current module // Hierarchical blocks instanciated (possibly indirectly) by current hierarchical block std::vector m_childrenp; // VISITORSs void visit(AstNodeModule*) override {} // Ignore all non AstModule void visit(AstModule* nodep) override { // Visit each module once if (nodep->user1SetOnce()) return; UINFO(5, "Visiting " << nodep->prettyNameQ()); VL_RESTORER(m_modp); m_modp = nodep; // If not a hierarchical block, just iterate and return if (!nodep->hierBlock()) { iterateChildrenConst(nodep); return; } // This is a hierarchical block, gather parts VL_RESTORER(m_params); VL_RESTORER(m_typeParams); VL_RESTORER(m_childrenp); m_params.clear(); m_typeParams.clear(); m_childrenp.clear(); iterateChildrenConst(nodep); // Create the graph vertex for this hier block V3HierBlock* const blockp = new V3HierBlock{m_graphp, nodep, m_params, m_typeParams}; // Record it m_mod2vtx[nodep] = blockp; // Add an edge to each child block for (V3HierBlock* const childp : m_childrenp) new V3GraphEdge{m_graphp, blockp, childp, 1}; } void visit(AstCell* nodep) override { // Nothing to do for non AstModules because hierarchical block cannot exist under them. AstModule* const modp = VN_CAST(nodep->modp(), Module); if (!modp) return; // Depth-first traversal of module hierechy iterateConst(modp); // If this is an instance of a hierarchical block, add to child array to link parent if (modp->hierBlock()) m_childrenp.emplace_back(m_mod2vtx.at(modp)); } void visit(AstVar* nodep) override { if (!m_modp) return; if (!m_modp->hierBlock()) return; // Can't handle interface port on hier block if (nodep->isIfaceRef() && !nodep->isIfaceParent()) { nodep->v3error("Modport cannot be used at the hierarchical block boundary"); } // Record overridden value parameter of this hier block if (nodep->isGParam() && nodep->overriddenParam()) { UASSERT_OBJ(m_modp, nodep, "Value parameter not under module"); m_params.push_back(nodep); } } void visit(AstParamTypeDType* nodep) override { UASSERT_OBJ(m_modp, nodep, "Type parameter not under module"); if (!m_modp->hierBlock()) return; // Record type parameter of this hier block m_typeParams.push_back(nodep); } void visit(AstNodeStmt*) override {} // Accelerate void visit(AstNodeExpr*) override {} // Accelerate void visit(AstConstPool*) override {} // Accelerate void visit(AstTypeTable*) override {} // Accelerate void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } // CONSTRUCTOR explicit HierBlockUsageCollectVisitor(AstNetlist* netlistp) { iterateChildrenConst(netlistp); if (dumpGraphLevel() >= 3) m_graphp->dumpDotFilePrefixed("hierblocks_initial"); // Simplify dependencies m_graphp->removeRedundantEdgesSum(&V3GraphEdge::followAlwaysTrue); // Topologically sorder the graph m_graphp->order(); // This is a bit heavy weight, but does produce a topological ordering if (dumpGraphLevel() >= 3) m_graphp->dumpDotFilePrefixed("hierblocks"); } public: static V3HierGraph* apply(AstNetlist* netlistp) { return HierBlockUsageCollectVisitor{netlistp}.m_graphp; } }; void V3HierGraph::writeCommandArgsFiles(bool forMkJson) const { for (const V3GraphVertex& vtx : vertices()) { vtx.as()->writeCommandArgsFile(forMkJson); } // For the top module const std::unique_ptr of{ V3File::new_ofstream(topCommandArgsFilename(forMkJson))}; if (!forMkJson) { // Load wrappers first not to be overwritten by the original HDL for (const V3GraphVertex& vtx : vertices()) { *of << vtx.as()->hierWrapperFilename(true) << "\n"; } } V3HierWriteCommonInputs(nullptr, of.get(), forMkJson); if (!forMkJson) { const VStringSet& cppFiles = v3Global.opt.cppFiles(); for (const string& i : cppFiles) *of << i << "\n"; *of << "--top-module " << v3Global.rootp()->topModulep()->name() << "\n"; *of << "--prefix " << v3Global.opt.prefix() << "\n"; *of << "-Mdir " << v3Global.opt.makeDir() << "\n"; *of << "--mod-prefix " << v3Global.opt.modPrefix() << "\n"; } for (const V3GraphVertex& vtx : vertices()) { *of << vtx.as()->hierBlockArgs().front() << "\n"; } if (!v3Global.opt.libCreate().empty()) { *of << "--lib-create " << v3Global.opt.libCreate() << "\n"; } if (v3Global.opt.protectKeyProvided()) { *of << "--protect-key " << v3Global.opt.protectKeyDefaulted() << "\n"; } *of << "--threads " << cvtToStr(v3Global.opt.threads()) << "\n"; *of << (v3Global.opt.systemC() ? "--sc" : "--cc") << "\n"; *of << v3Global.opt.allArgsStringForHierBlock(true) << "\n"; } string V3HierGraph::topCommandArgsFilename(bool forMkJson) { return V3HierCommandArgsFilename(v3Global.opt.prefix(), forMkJson); } void V3HierGraph::writeParametersFiles() const { for (const V3GraphVertex& vtx : vertices()) { vtx.as()->writeParametersFile(); } } //###################################################################### void V3Hierarchical::createGraph(AstNetlist* netlistp) { UASSERT(!v3Global.hierGraphp(), "Should only be called once"); AstNodeModule* const modp = netlistp->topModulep(); if (modp->hierBlock()) { modp->v3warn(HIERBLOCK, "Top module marked as hierarchical block, ignoring\n" + modp->warnMore() + "... Suggest remove verilator hier_block on this module"); modp->hierBlock(false); } V3HierGraph* const graphp = HierBlockUsageCollectVisitor::apply(netlistp); V3Stats::addStat("HierBlock, Hierarchical blocks", graphp->vertices().size()); // No hierarchical block is found, nothing to do. if (graphp->empty()) { VL_DO_DANGLING(delete graphp, graphp); return; } // Hold on to the graph v3Global.hierGraphp(graphp); }