diff --git a/bin/verilator b/bin/verilator index 453138e1b..d25f6e525 100755 --- a/bin/verilator +++ b/bin/verilator @@ -405,6 +405,7 @@ detailed descriptions of these arguments. -O3 High-performance optimizations -O Selectable optimizations -o Name of final executable + --output-groups Group .cpp files into larger ones --output-split Split .cpp files into pieces --output-split-cfuncs Split model functions --output-split-ctrace Split tracing functions diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index b46ca1c33..db705bc8c 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -958,6 +958,22 @@ Summary: delayed assignments. This option should only be used when suggested by the developers. +.. option:: --output-groups + + Enables concatenating the output .cpp files into the given number of + effective output .cpp files. This is useful if the compiler startup + overhead cumulated from compiling many small files becomes unacceptable, + which can happen in designs making extensive use of SystemVerilog classes, + templates or generate blocks. + + Using :vlopt:`--output-groups` can adversely impact caching and stability + (as in reproducibility) of compiled code. Compilation of larger .cpp + files also has higher memory requirements. Too low values might result in + swap thrashing with large designs, high values give no benefits. The + value should range from 2 to 20 for small to medium designs. + + Default is zero, which disables this feature. + .. option:: --output-split Enables splitting the output .cpp files into multiple outputs. When a diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index ecc8b8b22..cb50aaa1f 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2289,6 +2289,7 @@ public: class AstCFile final : public AstNodeFile { // C++ output file // Parents: NETLIST + uint64_t m_complexityScore = 0; bool m_slow : 1; ///< Compile w/o optimization bool m_source : 1; ///< Source file (vs header file) bool m_support : 1; ///< Support file (non systemc) @@ -2301,6 +2302,8 @@ public: ASTGEN_MEMBERS_AstCFile; void dump(std::ostream& str = std::cout) const override; void dumpJson(std::ostream& str = std::cout) const override; + uint64_t complexityScore() const { return m_complexityScore; } + void complexityScore(uint64_t newScore) { m_complexityScore = newScore; } bool slow() const { return m_slow; } void slow(bool flag) { m_slow = flag; } bool source() const { return m_source; } diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index 722c628e7..28bdf76a8 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -85,9 +85,38 @@ class EmitCBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst, public EmitCBa public: // STATE V3OutCFile* m_ofp = nullptr; + AstCFile* m_outFileNodep = nullptr; + int m_splitSize = 0; // # of cfunc nodes placed into output file bool m_trackText = false; // Always track AstText nodes // METHODS + + // Returns pointer to current output file object. V3OutCFile* ofp() const VL_MT_SAFE { return m_ofp; } + // Returns pointer to the AST node that represents the output file (`ofp()`) + AstCFile* outFileNodep() const VL_MT_SAFE { return m_outFileNodep; } + + // Sets ofp() and outFileNodep() to the given pointers, without closing a file these pointers + // currently point to. + void setOutputFile(V3OutCFile* ofp, AstCFile* nodep) { + m_ofp = ofp; + m_outFileNodep = nodep; + } + + // Sets ofp() and outFileNodep() to null, without closing a file these pointers currently point + // to. NOTE: Dummy nullptr argument is taken to make function calls more explicit. + void setOutputFile(std::nullptr_t nullp) { + UASSERT(nullp == nullptr, "Expected nullptr as the argument"); + m_ofp = nullp; + m_outFileNodep = nullp; + } + + // Closes current output file. Sets ofp() and outFileNodep() to nullptr. + void closeOutputFile() { + VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + m_outFileNodep->complexityScore(m_splitSize); + m_outFileNodep = nullptr; + } + void puts(const string& str) { ofp()->puts(str); } void putns(const AstNode* nodep, const string& str) { ofp()->putns(nodep, str); } void putsHeader() { ofp()->putsHeader(); } diff --git a/src/V3EmitCConstPool.cpp b/src/V3EmitCConstPool.cpp index 513d94e2e..c373e3160 100644 --- a/src/V3EmitCConstPool.cpp +++ b/src/V3EmitCConstPool.cpp @@ -30,6 +30,9 @@ VL_DEFINE_DEBUG_FUNCTIONS; // Const pool emitter class EmitCConstPool final : public EmitCConstInit { + // TYPES + using OutCFilePair = std::pair; + // MEMBERS uint32_t m_outFileCount = 0; int m_outFileSize = 0; @@ -38,17 +41,17 @@ class EmitCConstPool final : public EmitCConstInit { // METHODS - V3OutCFile* newOutCFile() const { + OutCFilePair newOutCFile() const { const string fileName = v3Global.opt.makeDir() + "/" + topClassName() + "__ConstPool_" + cvtToStr(m_outFileCount) + ".cpp"; - newCFile(fileName, /* slow: */ true, /* source: */ true); + AstCFile* const cfilep = newCFile(fileName, /* slow: */ true, /* source: */ true); V3OutCFile* const ofp = new V3OutCFile{fileName}; ofp->putsHeader(); ofp->puts("// DESCRIPTION: Verilator output: Constant pool\n"); ofp->puts("//\n"); ofp->puts("\n"); ofp->puts("#include \"verilated.h\"\n"); - return ofp; + return {ofp, cfilep}; } void maybeSplitCFile() { @@ -56,11 +59,12 @@ class EmitCConstPool final : public EmitCConstInit { // Splitting file, so using parallel build. v3Global.useParallelBuild(true); // Close current file - VL_DO_DANGLING(delete m_ofp, m_ofp); + closeOutputFile(); // Open next file m_outFileSize = 0; ++m_outFileCount; - m_ofp = newOutCFile(); + const OutCFilePair outFileAndNodePair = newOutCFile(); + setOutputFile(outFileAndNodePair.first, outFileAndNodePair.second); } void emitVars(const AstConstPool* poolp) { @@ -75,7 +79,8 @@ class EmitCConstPool final : public EmitCConstInit { return ap->name() < bp->name(); }); - m_ofp = newOutCFile(); + const OutCFilePair outFileAndNodePair = newOutCFile(); + setOutputFile(outFileAndNodePair.first, outFileAndNodePair.second); for (const AstVar* varp : varps) { maybeSplitCFile(); @@ -95,7 +100,7 @@ class EmitCConstPool final : public EmitCConstInit { } } - VL_DO_DANGLING(delete m_ofp, m_ofp); + closeOutputFile(); } // VISITORS diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 8e3056239..dcee1eabe 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -118,7 +118,6 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit { VMemberMap m_memberMap; AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting int m_labelNum = 0; // Next label number - int m_splitSize = 0; // # of cfunc nodes placed into output file bool m_inUC = false; // Inside an AstUCStmt or AstUCExpr bool m_emitConstInit = false; // Emitting constant initializer @@ -1455,9 +1454,9 @@ public: EmitCFunc() : m_lazyDecls(*this) {} - EmitCFunc(AstNode* nodep, V3OutCFile* ofp, bool trackText = false) + EmitCFunc(AstNode* nodep, V3OutCFile* ofp, AstCFile* cfilep, bool trackText = false) : EmitCFunc{} { - m_ofp = ofp; + setOutputFile(ofp, cfilep); m_trackText = trackText; iterateConst(nodep); } diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 36126ada1..839dc00ca 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -18,6 +18,7 @@ #include "V3EmitC.h" #include "V3EmitCConstInit.h" +#include "V3File.h" #include "V3UniqueNames.h" #include @@ -498,8 +499,11 @@ class EmitCHeader final : public EmitCConstInit { // Open output file const string filename = v3Global.opt.makeDir() + "/" + prefixNameProtect(modp) + ".h"; - newCFile(filename, /* slow: */ false, /* source: */ false); - m_ofp = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + AstCFile* const cfilep = newCFile(filename, /* slow: */ false, /* source: */ false); + V3OutCFile* const ofilep + = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + + setOutputFile(ofilep, cfilep); ofp()->putsHeader(); puts("// DESCRIPTION: Verilator output: Design internal header\n"); @@ -539,7 +543,7 @@ class EmitCHeader final : public EmitCConstInit { ofp()->putsEndGuard(); // Close output file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } ~EmitCHeader() override = default; diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index 7588aaa2d..5df77cf92 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -163,7 +163,7 @@ class EmitCImp final : EmitCFunc { // METHODS void openNextOutputFile(const std::set& headers, const string& subFileName) { - UASSERT(!m_ofp, "Output file already open"); + UASSERT(!ofp(), "Output file already open"); splitSizeReset(); // Reset file size tracking m_lazyDecls.reset(); // Need to emit new lazy declarations @@ -172,8 +172,10 @@ class EmitCImp final : EmitCFunc { // Unfortunately we have some lint checks here, so we can't just skip processing. // We should move them to a different stage. const string filename = VL_DEV_NULL; - m_cfilesr.push_back(createCFile(filename, /* slow: */ m_slow, /* source: */ true)); - m_ofp = new V3OutCFile{filename}; + AstCFile* const filep = createCFile(filename, /* slow: */ m_slow, /* source: */ true); + m_cfilesr.push_back(filep); + V3OutCFile* const ofilep = new V3OutCFile{filename}; + setOutputFile(ofilep, filep); } else { string filename = v3Global.opt.makeDir() + "/" + prefixNameProtect(m_fileModp); if (!subFileName.empty()) { @@ -182,8 +184,11 @@ class EmitCImp final : EmitCFunc { } if (m_slow) filename += "__Slow"; filename += ".cpp"; - m_cfilesr.push_back(createCFile(filename, /* slow: */ m_slow, /* source: */ true)); - m_ofp = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + AstCFile* const filep = createCFile(filename, /* slow: */ m_slow, /* source: */ true); + m_cfilesr.push_back(filep); + V3OutCFile* const ofilep + = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(ofilep, filep); } putsHeader(); @@ -477,7 +482,7 @@ class EmitCImp final : EmitCFunc { doCommonImp(classp); } - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } } void emitCFuncImp(const AstNodeModule* modp) { @@ -522,7 +527,7 @@ class EmitCImp final : EmitCFunc { iterateConst(funcp); } // Close output file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } } @@ -532,7 +537,7 @@ class EmitCImp final : EmitCFunc { // Splitting file, so using parallel build. v3Global.useParallelBuild(true); // Close old file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); // Open a new file openNextOutputFile(*m_requiredHeadersp, m_subFileName); } @@ -587,7 +592,7 @@ class EmitCTrace final : EmitCFunc { // METHODS void openNextOutputFile() { - UASSERT(!m_ofp, "Output file already open"); + UASSERT(!ofp(), "Output file already open"); splitSizeReset(); // Reset file size tracking m_lazyDecls.reset(); // Need to emit new lazy declarations @@ -602,11 +607,10 @@ class EmitCTrace final : EmitCFunc { cfilep->support(true); m_cfilesr.push_back(cfilep); - if (optSystemC()) { - m_ofp = new V3OutScFile{filename}; - } else { - m_ofp = new V3OutCFile{filename}; - } + V3OutCFile* const ofilep + = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(ofilep, cfilep); + putsHeader(); puts("// DESCR" "IPTION: Verilator output: Tracing implementation internals\n"); @@ -904,7 +908,7 @@ class EmitCTrace final : EmitCFunc { // Splitting file, so using parallel build. v3Global.useParallelBuild(true); // Close old file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); // Open a new file openNextOutputFile(); } @@ -956,7 +960,7 @@ class EmitCTrace final : EmitCFunc { if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) iterateConst(funcp); } // Close output file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); if (m_slow) { callTypeSubs(); closeTypesFile(); @@ -1023,7 +1027,7 @@ void V3EmitC::emitcFiles() { V3OutCFile of{cfilep->name()}; of.puts("// DESCR" "IPTION: Verilator generated C++\n"); - const EmitCFunc visitor{cfilep->tblockp(), &of, true}; + const EmitCFunc visitor{cfilep->tblockp(), &of, cfilep, true}; } } } diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 75789c798..10bbd8769 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -42,9 +42,9 @@ private: // MAIN METHOD void emitInt() { const string filename = v3Global.opt.makeDir() + "/" + topClassName() + "__main.cpp"; - newCFile(filename, false /*slow*/, true /*source*/); + AstCFile* const cfilep = newCFile(filename, false /*slow*/, true /*source*/); V3OutCFile cf{filename}; - m_ofp = &cf; + setOutputFile(&cf, cfilep); // Not defining main_time/vl_time_stamp, so v3Global.opt.addCFlags("-DVL_TIME_CONTEXT"); // On MSVC++ anyways @@ -116,7 +116,7 @@ private: puts("return 0;\n"); puts("}\n"); - m_ofp = nullptr; + setOutputFile(nullptr); } }; diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 9baeb70e3..54f625d13 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -53,11 +53,12 @@ class EmitCModel final : public EmitCFunc { } void emitHeader(AstNodeModule* modp) { - UASSERT(!m_ofp, "Output file should not be open"); + UASSERT(!ofp(), "Output file should not be open"); const string filename = v3Global.opt.makeDir() + "/" + topClassName() + ".h"; - newCFile(filename, /* slow: */ false, /* source: */ false); - m_ofp = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(v3Global.opt.systemC() ? new V3OutScFile{filename} + : new V3OutCFile{filename}, + newCFile(filename, /* slow: */ false, /* source: */ false)); ofp()->putsHeader(); puts("// DESCRIPTION: Verilator output: Primary model header\n"); @@ -269,7 +270,7 @@ class EmitCModel final : public EmitCFunc { ofp()->putsEndGuard(); - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } void emitConstructorImplementation(AstNodeModule* modp) { @@ -612,11 +613,12 @@ class EmitCModel final : public EmitCFunc { } void emitImplementation(AstNodeModule* modp) { - UASSERT(!m_ofp, "Output file should not be open"); + UASSERT(!ofp(), "Output file should not be open"); const string filename = v3Global.opt.makeDir() + "/" + topClassName() + ".cpp"; - newCFile(filename, /* slow: */ false, /* source: */ true); - m_ofp = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(v3Global.opt.systemC() ? new V3OutScFile{filename} + : new V3OutCFile{filename}, + newCFile(filename, /* slow: */ false, /* source: */ true)); ofp()->putsHeader(); puts("// DESCRIPTION: Verilator output: " @@ -635,11 +637,11 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.trace()) emitTraceMethods(modp); if (v3Global.opt.savable()) emitSerializationFunctions(); - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } void emitDpiExportDispatchers(AstNodeModule* modp) { - UASSERT(!m_ofp, "Output file should not be open"); + UASSERT(!ofp(), "Output file should not be open"); // Emit DPI Export dispatchers for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { @@ -650,19 +652,19 @@ class EmitCModel final : public EmitCFunc { // Splitting file, so using parallel build. v3Global.useParallelBuild(true); // Close old file - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } - if (!m_ofp) { + if (!ofp()) { string filename = v3Global.opt.makeDir() + "/" + topClassName() + "__Dpi_Export"; filename = m_uniqueNames.get(filename); filename += ".cpp"; - newCFile(filename, /* slow: */ false, /* source: */ true); - m_ofp = v3Global.opt.systemC() ? new V3OutScFile{filename} - : new V3OutCFile{filename}; + setOutputFile(v3Global.opt.systemC() ? new V3OutScFile{filename} + : new V3OutCFile{filename}, + newCFile(filename, /* slow: */ false, /* source: */ true)); splitSizeReset(); // Reset file size tracking m_lazyDecls.reset(); - m_ofp->putsHeader(); + ofp()->putsHeader(); puts( "// DESCRIPTION: Verilator output: Implementation of DPI export functions.\n"); puts("//\n"); @@ -675,7 +677,7 @@ class EmitCModel final : public EmitCFunc { iterateConst(funcp); } - if (m_ofp) VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + if (ofp()) closeOutputFile(); } void main(AstNodeModule* modp) { diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 768c1409b..14323fed6 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -403,13 +403,9 @@ public: void EmitCSyms::emitSymHdr() { UINFO(6, __FUNCTION__ << ": " << endl); const string filename = v3Global.opt.makeDir() + "/" + symClassName() + ".h"; - newCFile(filename, true /*slow*/, false /*source*/); - - if (v3Global.opt.systemC()) { - m_ofp = new V3OutScFile{filename}; - } else { - m_ofp = new V3OutCFile{filename}; - } + AstCFile* const cfilep = newCFile(filename, true /*slow*/, false /*source*/); + V3OutCFile* const ofilep = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(ofilep, cfilep); ofp()->putsHeader(); puts("// DESCRIPTION: Verilator output: Symbol table internal header\n"); @@ -586,18 +582,18 @@ void EmitCSyms::emitSymHdr() { puts("};\n"); ofp()->putsEndGuard(); - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } void EmitCSyms::closeSplit() { - if (!m_ofp || m_ofp == m_ofpBase) return; + if (!ofp() || ofp() == m_ofpBase) return; puts("}\n"); - VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr); + closeOutputFile(); } void EmitCSyms::checkSplit(bool usesVfinal) { - if (m_ofp + if (ofp() && (!v3Global.opt.outputSplitCFuncs() || m_numStmts < v3Global.opt.outputSplitCFuncs())) { return; } @@ -613,11 +609,8 @@ void EmitCSyms::checkSplit(bool usesVfinal) { m_usesVfinal[m_funcNum] = usesVfinal; closeSplit(); - if (v3Global.opt.systemC()) { - m_ofp = new V3OutScFile{filename}; - } else { - m_ofp = new V3OutCFile{filename}; - } + V3OutCFile* const ofilep = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(ofilep, cfilep); m_ofpBase->puts(symClassName() + "_" + cvtToStr(m_funcNum) + "("); if (usesVfinal) m_ofpBase->puts("__Vfinal"); @@ -697,13 +690,10 @@ void EmitCSyms::emitSymImp() { AstCFile* const cfilep = newCFile(filename, true /*slow*/, true /*source*/); cfilep->support(true); - if (v3Global.opt.systemC()) { - m_ofp = new V3OutScFile{filename}; - } else { - m_ofp = new V3OutCFile{filename}; - } + V3OutCFile* const ofilep = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; + setOutputFile(ofilep, cfilep); - m_ofpBase = m_ofp; + m_ofpBase = ofp(); emitSymImpPreamble(); if (v3Global.opt.savable()) { @@ -1033,7 +1023,7 @@ void EmitCSyms::emitSymImp() { m_ofpBase->puts("}\n"); closeSplit(); - m_ofp = nullptr; + setOutputFile(nullptr); VL_DO_CLEAR(delete m_ofpBase, m_ofpBase = nullptr); } @@ -1045,9 +1035,9 @@ void EmitCSyms::emitDpiHdr() { AstCFile* const cfilep = newCFile(filename, false /*slow*/, false /*source*/); cfilep->support(true); V3OutCFile hf{filename}; - m_ofp = &hf; + setOutputFile(&hf, cfilep); - m_ofp->putsHeader(); + ofp()->putsHeader(); puts("// DESCR" "IPTION: Verilator output: Prototypes for DPI import and export functions.\n"); puts("//\n"); @@ -1089,7 +1079,7 @@ void EmitCSyms::emitDpiHdr() { puts("#endif\n"); ofp()->putsEndGuard(); - m_ofp = nullptr; + setOutputFile(nullptr); } //###################################################################### @@ -1100,9 +1090,9 @@ void EmitCSyms::emitDpiImp() { AstCFile* const cfilep = newCFile(filename, false /*slow*/, true /*source*/); cfilep->support(true); V3OutCFile hf(filename); - m_ofp = &hf; + setOutputFile(&hf, cfilep); - m_ofp->putsHeader(); + ofp()->putsHeader(); puts("// DESCR" "IPTION: Verilator output: Implementation of DPI export functions.\n"); puts("//\n"); @@ -1146,7 +1136,7 @@ void EmitCSyms::emitDpiImp() { puts("\n"); } } - m_ofp = nullptr; + setOutputFile(nullptr); } //###################################################################### diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 2022aea9b..48c6ad247 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -24,18 +24,502 @@ VL_DEFINE_DEBUG_FUNCTIONS; +// Groups adjacent files in a list, evenly distributing sum of scores +class EmitGroup final { +public: + struct FileOrConcatenatedFilesList final { + const std::string m_filename; // Filename or output group filename if grouping + std::vector m_concatenatedFilenames; // Grouped filenames if grouping + + bool isConcatenatingFile() const { return !m_concatenatedFilenames.empty(); } + }; + + struct FilenameWithScore final { + const std::string m_filename; // Input filename + const uint64_t m_score; // Input file complexity + }; + +private: + // Data of a single work unit used in `singleConcatenatedFilesList()`. + struct WorkList final { + uint64_t m_totalScore = 0; // Sum of scores of included files + std::vector m_files; // Included filenames + int m_bucketsNum + = 0; // Number of buckets assigned for this list. Used only in concatenable lists. + bool m_isConcatenable = true; // Indicated whether files on this list can be concatenated. + const int m_dbgId; // Work list ID for debugging. + + WorkList() = delete; + WorkList(int id) + : m_dbgId{id} {} + }; + + // MAIN PARAMETERS + + // Minimum number of input files required to perform concatenation. + // Concatenation of a small number of files does not give any performance advantages. + // The value has been chosen arbitrarily, most likely could be larger. + static constexpr size_t MIN_FILES_COUNT = 16; + + // Concatenation of only a few files most likely does not increase performance. + // The value has been chosen arbitrarily. + static constexpr size_t MIN_FILES_PER_BUCKET = 4; + + // MEMBERS + + const std::vector + m_inputFiles; // List of filenames from initial AstCFile list + std::vector + m_outputFiles; // Output list of files and group files + const uint64_t m_totalScore; // Sum of file scores + const std::string m_groupFilePrefix; // Prefix for output group filenames + std::vector m_workLists; // Lists of small enough files + std::unique_ptr m_logp; // Dump file + std::vector m_concatenableListsByDescSize; // Lists sorted by size, descending + + EmitGroup(std::vector inputFiles, uint64_t totalScore, + std::string groupFilePrefix) + : m_inputFiles{std::move(inputFiles)} + , m_totalScore{totalScore} + , m_groupFilePrefix{groupFilePrefix} {} + + // Debug logging: prints scores histogram + void dumpLogScoreHistogram(std::ostream& os) { + constexpr int MAX_BAR_LENGTH = 80; + constexpr int MAX_INTERVALS_NUM = 60; + + // All scores arranged in ascending order. + std::vector sortedScores; + sortedScores.reserve(m_inputFiles.size()); + std::transform(m_inputFiles.begin(), m_inputFiles.end(), std::back_inserter(sortedScores), + [](const FilenameWithScore& inputFile) { return inputFile.m_score; }); + std::sort(sortedScores.begin(), sortedScores.end()); + + const int64_t topScore = sortedScores.back(); + os << "Top score: " << topScore << endl; + const int maxScoreWidth = std::to_string(topScore).length(); + + const int64_t intervalsNum = std::min(topScore + 1, MAX_INTERVALS_NUM); + + struct Interval final { + uint64_t m_lowerBound = 0; + int m_size = 0; + }; + + std::vector intervals; + intervals.resize(intervalsNum); + + intervals[0].m_lowerBound = 0; + for (int i = 1; i < intervalsNum; i++) { + intervals[i].m_lowerBound = (topScore + 1) * i / intervalsNum + 1; + } + + for (const uint64_t score : sortedScores) { + const unsigned int ivIdx = score * intervalsNum / (topScore + 1); + ++intervals[ivIdx].m_size; + } + + int topIntervalSize = 0; + for (const Interval& iv : intervals) + topIntervalSize = std::max(topIntervalSize, iv.m_size); + + os << "Input files' scores histogram:" << endl; + + for (const Interval& iv : intervals) { + const int scaledSize = iv.m_size * (MAX_BAR_LENGTH + 1) / topIntervalSize; + std::string line = " |" + std::string(scaledSize, '#'); + + os << std::setw(maxScoreWidth) << iv.m_lowerBound << line << " " << iv.m_size << '\n'; + } + os << std::setw(maxScoreWidth) << (topScore + 1) << '\n'; + os << endl; + } + + // PRIVATE METHODS + + // Debug logging: dumps Work Lists and their lists of files + void dumpWorkLists(std::ostream& os) { + os << "Initial Work Lists with their concatenation eligibility status:" << endl; + for (const WorkList& list : m_workLists) { + os << "+ [" << (list.m_isConcatenable ? 'x' : ' ') << "] Work List #" << list.m_dbgId + << " (num of files: " << list.m_files.size() + << "; total score: " << list.m_totalScore << ")\n"; + if (debug() >= 6 || list.m_files.size() < 4) { + // Log all files + for (const FilenameWithScore& file : list.m_files) + os << "| + " << file.m_filename << " (score: " << file.m_score << ")\n"; + } else { + // Log only first and last file + const FilenameWithScore& first = list.m_files.front(); + const FilenameWithScore& last = list.m_files.back(); + os << "| + " << first.m_filename << " (score: " << first.m_score << ")\n"; + os << "| | (... " << (list.m_files.size() - 2) << " files ...)\n"; + os << "| + " << last.m_filename << " (score: " << last.m_score << ")\n"; + } + } + os << endl; + } + + // Debug logging: dumps list of output files. List of grouped files is additionally printed + // for each concatenating file. + void dumpOutputList(std::ostream& os) const { + os << "List of output files after execution of concatenation:" << endl; + + for (const FileOrConcatenatedFilesList& entry : m_outputFiles) { + if (entry.isConcatenatingFile()) { + os << "+ " << entry.m_filename << " (concatenating file)\n"; + for (const string& f : entry.m_concatenatedFilenames) { + os << "| + " << f << '\n'; + } + } else { + os << "+ " << entry.m_filename << '\n'; + } + } + } + + // Called when the concatenation is aborted, creates an identity mapping + bool fallbackNoGrouping(size_t inputFilesCount) { + const int totalBucketsNum = v3Global.opt.outputGroups(); + // Return early if there's nothing to do. + bool groupingRedundant = false; + if (inputFilesCount < MIN_FILES_COUNT) { + UINFO(4, "File concatenation skipped: Too few files (" + << m_inputFiles.size() << " < " << MIN_FILES_COUNT << ")" << endl); + groupingRedundant = true; + } + if (inputFilesCount < (MIN_FILES_PER_BUCKET * totalBucketsNum)) { + UINFO(4, "File concatenation skipped: Too few files per bucket (" + << m_inputFiles.size() << " < " << MIN_FILES_PER_BUCKET << " - " + << totalBucketsNum << ")" << endl); + groupingRedundant = true; + } + if (!groupingRedundant) return false; + + m_outputFiles.reserve(m_inputFiles.size()); + for (const FilenameWithScore& filename : m_inputFiles) { + m_outputFiles.push_back({std::move(filename.m_filename), {}}); + } + return true; + } + + void createWorkLists() { + // Create initial Work Lists. + + const int totalBucketsNum = v3Global.opt.outputGroups(); + // Input files with a score exceeding this value are excluded from concatenation. + const uint64_t concatenableFileMaxScore = m_totalScore / totalBucketsNum / 2; + + V3Stats::addStat("Concatenation max score", concatenableFileMaxScore); + int nextWorkListId = 0; + + if (m_logp) *m_logp << "Input files with their concatenation eligibility status:" << endl; + for (const FilenameWithScore& inputFile : m_inputFiles) { + const bool fileIsConcatenable = (inputFile.m_score <= concatenableFileMaxScore); + if (m_logp) + *m_logp << " + [" << (fileIsConcatenable ? 'x' : ' ') << "] " + << inputFile.m_filename << " (score: " << inputFile.m_score << ")" + << endl; + V3Stats::addStatSum(fileIsConcatenable ? "Concatenation total grouped score" + : "Concatenation total non-grouped score", + inputFile.m_score); + // Add new list if the last list's concatenability does not match the inputFile's + // concatenability + if (m_workLists.empty() || m_workLists.back().m_isConcatenable != fileIsConcatenable) { + m_workLists.push_back(WorkList{nextWorkListId++}); + m_workLists.back().m_isConcatenable = fileIsConcatenable; + } + // Add inputFile to the last list + WorkList& list = m_workLists.back(); + list.m_files.push_back({inputFile.m_filename, inputFile.m_score}); + list.m_totalScore += inputFile.m_score; + } + if (m_logp) *m_logp << endl; + } + + void assignBuckets(uint64_t concatenableFilesTotalScore) { + // Assign buckets to lists + + const size_t totalBucketsNum = v3Global.opt.outputGroups(); + + // More concatenable lists than buckets. Exclude lists with lowest number of files. + // Does not happen very often due to files being already filtered out by comparison of + // their score to ConcatenableFilesMaxScore. + if (m_concatenableListsByDescSize.size() > totalBucketsNum) { + // Debugging: Log which work lists will be kept + if (m_logp) { + *m_logp << "More Work Lists than buckets; " + "Work Lists with statuses indicating whether the list will be kept:" + << endl; + // Only lists that will be kept. List that will be removed are logged below. + std::for_each(m_concatenableListsByDescSize.begin(), + m_concatenableListsByDescSize.begin() + totalBucketsNum, + [&](WorkList* listp) { + *m_logp << "+ [x] Work List #" << listp->m_dbgId + << " (num of files: " << listp->m_files.size() + << "; total score: " << listp->m_totalScore << ")\n"; + }); + } + // NOTE: Not just debug logging - notice `isConcatenable` assignment in the loop. + std::for_each(m_concatenableListsByDescSize.begin() + totalBucketsNum, + m_concatenableListsByDescSize.end(), [&](WorkList* listp) { + listp->m_isConcatenable = false; + + if (m_logp) + *m_logp << "+ [ ] Work List #" << listp->m_dbgId + << " (num of files: " << listp->m_files.size() + << "; total score: " << listp->m_totalScore << ")\n"; + }); + if (m_logp) *m_logp << endl; + + m_concatenableListsByDescSize.resize(totalBucketsNum); + // Recalculate stats + concatenableFilesTotalScore = 0; + for (WorkList* listp : m_concatenableListsByDescSize) { + concatenableFilesTotalScore += listp->m_totalScore; + } + } + + const uint64_t idealBucketScore = concatenableFilesTotalScore / totalBucketsNum; + + V3Stats::addStat("Concatenation ideal bucket score", idealBucketScore); + + if (m_logp) *m_logp << "Buckets assigned to Work Lists:" << endl; + int availableBuckets = v3Global.opt.outputGroups(); + for (WorkList* listp : m_concatenableListsByDescSize) { + if (availableBuckets > 0) { + listp->m_bucketsNum = std::min( + availableBuckets, std::max(1, listp->m_totalScore / idealBucketScore)); + availableBuckets -= listp->m_bucketsNum; + if (m_logp) + *m_logp << "+ [" << std::setw(2) << listp->m_bucketsNum << "] Work List #" + << listp->m_dbgId << '\n'; + } else { + // Out of buckets. Instead of recalculating everything just exclude the list. + listp->m_isConcatenable = false; + if (m_logp) + *m_logp << "+ [ 0] Work List #" << std::left << std::setw(4) << listp->m_dbgId + << std::right << " (excluding from concatenation)\n"; + } + } + if (m_logp) *m_logp << endl; + } + + void buildOutputList() { + // Assign files to buckets and build final list of files + + // At this point the workLists contains concatenatable file lists separated by one or more + // non-concatenable file lists. Each concatenatable list has N buckets (where N > 0) + // assigned to it, which have to be filled with files from this list. Ideally, sum of file + // scores in every bucket should be the same. + int concatenatedFileId = 0; + for (WorkList& list : m_workLists) { + if (!list.m_isConcatenable) { + for (FilenameWithScore& file : list.m_files) { + m_outputFiles.push_back({std::move(file.m_filename), {}}); + } + continue; + } + + // Ideal bucket score limited to buckets and score of the current Work List. + const uint64_t listIdealBucketScore = list.m_totalScore / list.m_bucketsNum; + + auto fileIt = list.m_files.begin(); + for (int i = 0; i < list.m_bucketsNum; ++i) { + FileOrConcatenatedFilesList bucket{v3Global.opt.prefix() + "_" + m_groupFilePrefix + + std::to_string(concatenatedFileId++), + {}}; + + uint64_t bucketScore = 0; + + for (; fileIt != list.m_files.end(); ++fileIt) { + const uint64_t diffNow + = std::abs((int64_t)(listIdealBucketScore - bucketScore)); + const uint64_t diffIfAdded = std::abs( + (int64_t)(listIdealBucketScore - bucketScore - fileIt->m_score)); + if (bucketScore == 0 || fileIt->m_score == 0 || diffNow > diffIfAdded) { + // Bucket score will be better with the file in it. + bucketScore += fileIt->m_score; + bucket.m_concatenatedFilenames.push_back(std::move(fileIt->m_filename)); + } else { + // Best possible bucket score reached, process next bucket. + break; + } + } + + if (bucket.m_concatenatedFilenames.size() == 1) { + // Unwrap the bucket if it contains only one file. + m_outputFiles.push_back( + {std::move(bucket.m_concatenatedFilenames.front()), {}}); + } else if (bucket.m_concatenatedFilenames.size() > 1) { + m_outputFiles.push_back(std::move(bucket)); + } + // Most likely no bucket will be empty in normal situations. If it happen the + // bucket will just be dropped. + } + for (; fileIt != list.m_files.end(); ++fileIt) { + // The Work List is out of buckets, but some files were left. + // Add them to the last bucket. + m_outputFiles.back().m_concatenatedFilenames.push_back(fileIt->m_filename); + } + } + } + + void assertFilesSame() const { + auto ifIt = m_inputFiles.begin(); + auto ofIt = m_outputFiles.begin(); + while (ifIt != m_inputFiles.end() && ofIt != m_outputFiles.end()) { + if (ofIt->isConcatenatingFile()) { + for (const string& ocf : ofIt->m_concatenatedFilenames) { + UASSERT(ifIt != m_inputFiles.end(), + "More output files than input files. First extra file: " << ocf); + UASSERT(ifIt->m_filename == ocf, + "Name mismatch: " << ifIt->m_filename << " != " << ocf); + ++ifIt; + } + ++ofIt; + } else { + UASSERT(ifIt->m_filename == ofIt->m_filename, + "Name mismatch: " << ifIt->m_filename << " != " << ofIt->m_filename); + ++ifIt; + ++ofIt; + } + } + UASSERT(ifIt == m_inputFiles.end(), + "More input files than input files. First extra file: " << ifIt->m_filename); + UASSERT(ofIt == m_outputFiles.end(), + "More output files than input files. First extra file: " << ofIt->m_filename); + } + + void process() { + UINFO(4, __FUNCTION__ << ":" << endl); + UINFO(5, "Number of input files: " << m_inputFiles.size() << endl); + UINFO(5, "Total score: " << m_totalScore << endl); + UINFO(5, "Group file prefix: " << m_groupFilePrefix << endl); + + const int totalBucketsNum = v3Global.opt.outputGroups(); + UINFO(5, "Number of buckets: " << totalBucketsNum << endl); + UASSERT(totalBucketsNum > 0, "More than 0 buckets required"); + + if (fallbackNoGrouping(m_inputFiles.size())) return; + + if (dumpLevel() >= 6) { + const string filename = v3Global.debugFilename("outputgroup") + ".txt"; + m_logp = std::unique_ptr{V3File::new_ofstream(filename)}; + if (m_logp->fail()) v3fatal("Can't write " << filename); + } + + if (m_logp) dumpLogScoreHistogram(*m_logp); + + createWorkLists(); + + // Collect stats and mark lists with only one file as non-concatenable + + size_t concatenableFilesCount = 0; + int64_t concatenableFilesTotalScore = 0; + + for (WorkList& list : m_workLists) { + if (!list.m_isConcatenable) continue; + + // "Concatenation" of a single file is pointless + if (list.m_files.size() == 1) { + list.m_isConcatenable = false; + UINFO(5, "Excluding from concatenation: Work List contains only one file: " + "Work List #" + << list.m_dbgId << endl); + continue; + } + + concatenableFilesCount += list.m_files.size(); + concatenableFilesTotalScore += list.m_totalScore; + // This vector is sorted below + m_concatenableListsByDescSize.push_back(&list); + } + + if (m_logp) dumpWorkLists(*m_logp); + + // Check concatenation conditions again using more precise data + if (fallbackNoGrouping(concatenableFilesCount)) return; + + std::sort(m_concatenableListsByDescSize.begin(), m_concatenableListsByDescSize.end(), + [](const WorkList* ap, const WorkList* bp) { + // Sort in descending order by number of files + if (ap->m_files.size() != bp->m_files.size()) + return (ap->m_files.size() > bp->m_files.size()); + // As a fallback sort in ascending order by totalSize. This makes lists + // with higher score more likely to be excluded. + return bp->m_totalScore > ap->m_totalScore; + }); + + assignBuckets(concatenableFilesTotalScore); + + buildOutputList(); + + if (m_logp) dumpOutputList(*m_logp); + assertFilesSame(); + } + +public: + static std::vector + singleConcatenatedFilesList(std::vector inputFiles, uint64_t totalScore, + std::string groupFilePrefix) { + EmitGroup group{std::move(inputFiles), totalScore, groupFilePrefix}; + group.process(); + return group.m_outputFiles; + } +}; + // ###################################################################### // Emit statements and expressions class EmitMk final { + using FileOrConcatenatedFilesList = EmitGroup::FileOrConcatenatedFilesList; + using FilenameWithScore = EmitGroup::FilenameWithScore; + public: // METHODS + static void emitConcatenatingFile(const FileOrConcatenatedFilesList& entry) { + UASSERT(entry.isConcatenatingFile(), "Passed entry does not represent concatenating file"); + + V3OutCFile concatenatingFile{v3Global.opt.makeDir() + "/" + entry.m_filename + ".cpp"}; + concatenatingFile.putsHeader(); + for (const string& file : entry.m_concatenatedFilenames) { + concatenatingFile.puts("#include \"" + file + ".cpp\"\n"); + } + } + void putMakeClassEntry(V3OutMkFile& of, const string& name) { of.puts("\t" + V3Os::filenameNonDirExt(name) + " \\\n"); } void emitClassMake() { + std::vector vmClassesSlowList; + std::vector vmClassesFastList; + if (v3Global.opt.outputGroups() > 0) { + std::vector slowFiles; + std::vector fastFiles; + uint64_t slowTotalScore = 0; + uint64_t fastTotalScore = 0; + + for (AstNodeFile* nodep = v3Global.rootp()->filesp(); nodep; + nodep = VN_AS(nodep->nextp(), NodeFile)) { + const AstCFile* const cfilep = VN_CAST(nodep, CFile); + if (cfilep && cfilep->source() && cfilep->support() == false) { + std::vector& files = cfilep->slow() ? slowFiles : fastFiles; + uint64_t& totalScore = cfilep->slow() ? slowTotalScore : fastTotalScore; + + totalScore += cfilep->complexityScore(); + files.push_back( + {V3Os::filenameNonDirExt(cfilep->name()), cfilep->complexityScore()}); + } + } + + vmClassesSlowList = EmitGroup::singleConcatenatedFilesList( + std::move(slowFiles), slowTotalScore, "vm_classes_Slow_"); + vmClassesFastList = EmitGroup::singleConcatenatedFilesList( + std::move(fastFiles), fastTotalScore, "vm_classes_"); + } + // Generate the makefile V3OutMkFile of{v3Global.opt.makeDir() + "/" + v3Global.opt.prefix() + "_classes.mk"}; of.putsHeader(); @@ -113,6 +597,13 @@ public: putMakeClassEntry(of, "verilated_profiler.cpp"); } } else if (support == 2 && slow) { + } else if (support == 0 && v3Global.opt.outputGroups() > 0) { + const std::vector& list + = slow ? vmClassesSlowList : vmClassesFastList; + for (const FileOrConcatenatedFilesList& entry : list) { + if (entry.isConcatenatingFile()) { emitConcatenatingFile(entry); } + putMakeClassEntry(of, entry.m_filename); + } } else { for (AstNodeFile* nodep = v3Global.rootp()->filesp(); nodep; nodep = VN_AS(nodep->nextp(), NodeFile)) { diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 0c22a4b6e..d111bfeb3 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1408,6 +1408,10 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, fl->v3error("--output-split-ctrace must be >= 0: " << valp); } }); + DECL_OPTION("-output-groups", CbVal, [this, fl](const char* valp) { + m_outputGroups = std::atoi(valp); + if (m_outputGroups < 0) { fl->v3error("--output-groups must be >= 0: " << valp); } + }); DECL_OPTION("-P", Set, &m_preprocNoLine); DECL_OPTION("-pvalue+", CbPartialMatch, diff --git a/src/V3Options.h b/src/V3Options.h index bd2a40d1c..2ac99bf04 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -316,6 +316,7 @@ private: VOptionBool m_makeDepend; // main switch: -MMD int m_maxNumWidth = 65536; // main switch: --max-num-width int m_moduleRecursion = 100; // main switch: --module-recursion-depth + int m_outputGroups = 0; // main switch: --output-groups int m_outputSplit = 20000; // main switch: --output-split int m_outputSplitCFuncs = -1; // main switch: --output-split-cfuncs int m_outputSplitCTrace = -1; // main switch: --output-split-ctrace @@ -565,6 +566,7 @@ public: int outputSplit() const { return m_outputSplit; } int outputSplitCFuncs() const { return m_outputSplitCFuncs; } int outputSplitCTrace() const { return m_outputSplitCTrace; } + int outputGroups() const { return m_outputGroups; } int pinsBv() const VL_MT_SAFE { return m_pinsBv; } int publicDepth() const { return m_publicDepth; } int reloopLimit() const { return m_reloopLimit; } diff --git a/test_regress/t/t_flag_csplit_groups.py b/test_regress/t/t_flag_csplit_groups.py new file mode 100755 index 000000000..8fe9707ba --- /dev/null +++ b/test_regress/t/t_flag_csplit_groups.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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('vlt_all') + +test.top_filename = "t/t_flag_csplit.v" + + +def check_splits(): + got1 = False + gotSyms1 = False + for filename in test.glob_some(test.obj_dir + "/*.cpp"): + if re.search(r'Syms__1', filename): + gotSyms1 = True + elif re.search(r'__1', filename): + got1 = True + if not got1: + test.error("No __1 split file found") + if not gotSyms1: + test.error("No Syms__1 split file found") + + +def check_no_all_file(): + for filename in test.glob_some(test.obj_dir + "/*.cpp"): + if re.search(r'__ALL.cpp', filename): + test.error("__ALL.cpp file found: " + filename) + + +def check_cpp(filename): + size = os.path.getsize(filename) + if test.verbose: + print(" File %6d %s\n" % (size, filename)) + funcs = [] + with open(filename, 'r', encoding="utf8") as fh: + for line in fh: + m = re.search(r'^(void|IData)\s+(.*::.*){', line) + if not m: + continue + func = m.group(2) + func = re.sub(r'\(.*$', '', func) + if test.verbose: + print("\tFunc " + func) + if (re.search(r'(::_eval_initial_loop$', func) or re.search(r'::__Vconfigure$', func) + or re.search(r'::trace$', func) or re.search(r'::traceInit$', func) + or re.search(r'::traceFull$', func) or re.search(r'::final$', func) + or re.search(r'::prepareClone$', func) or re.search(r'::atClone$', func)): + continue + funcs.append(func) + + if len(funcs) > 0: + test.error("Split had multiple functions in $filename\n\t" + "\n\t".join(funcs)) + + +def check_gcc_flags(filename): + with open(filename, 'r', encoding="utf8") as fh: + for line in fh: + line = line.rstrip() + if test.verbose: + print(":log: " + line) + if re.search(r'' + test.vm_prefix + r'\S*\.cpp', line): + filetype = "slow" if re.search(r'(Slow|Syms)', line) else "fast" + opt = "fast" if re.search(r'-O2', line) else "slow" + if test.verbose: + print(filetype + ", " + opt + ", " + line) + if filetype != opt: + test.error(filetype + " file compiled as if was " + opt + ": " + line) + elif re.search(r'.cpp', line) and not re.search(r'-Os', line): + test.error("library file not compiled with OPT_GLOBAL: " + line) + + +# This rule requires GNU make > 4.1 (or so, known broken in 3.81) +#%__Slow.o: %__Slow.cpp +# $(OBJCACHE) $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OPT_SLOW) -c -o $@ $< +if not test.make_version or float(test.make_version) < 4.1: + test.skip("Test requires GNU Make version >= 4.1") + +test.compile(v_flags2=["--trace", + "--output-split 1", + "--output-groups 2", + "--output-split-cfuncs 1", + "--exe", + "../" + test.main_filename], + verilator_make_gmake=False) # yapf:disable + +# We don't use the standard test_regress rules, as want to test the rules +# properly build +test.run(logfile=test.obj_dir + "/vlt_gcc.log", + tee=test.verbose, + cmd=[os.environ["MAKE"], + "-C " + test.obj_dir, + "-f "+test.vm_prefix+".mk", + "-j 4", + "VM_PREFIX="+test.vm_prefix, + "TEST_OBJ_DIR="+test.obj_dir, + "CPPFLAGS_DRIVER=-D"+test.name.upper(), + ("CPPFLAGS_DRIVER2=-DTEST_VERBOSE=1" if test.verbose else ""), + "OPT_FAST=-O2", + "OPT_SLOW=-O0", + "OPT_GLOBAL=-Os", + ]) # yapf:disable + +test.execute() + +# Splitting should set VM_PARALLEL_BUILDS to 1 by default +test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", r'VM_PARALLEL_BUILDS\s*=\s*1') +check_splits() +check_no_all_file() +check_gcc_flags(test.obj_dir + "/vlt_gcc.log") + +# Check that only vm_classes_*.cpp are to be compiled +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "sub") +test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_1") +test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_1") +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_2") +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2") + +test.passes() diff --git a/test_regress/t/t_flag_output_groups_bad.out b/test_regress/t/t_flag_output_groups_bad.out new file mode 100644 index 000000000..a52d629fd --- /dev/null +++ b/test_regress/t/t_flag_output_groups_bad.out @@ -0,0 +1,2 @@ +%Error: --output-groups must be >= 0: -1 +%Error: Exiting due to diff --git a/test_regress/t/t_flag_output_groups_bad.py b/test_regress/t/t_flag_output_groups_bad.py new file mode 100755 index 000000000..1a3a5346b --- /dev/null +++ b/test_regress/t/t_flag_output_groups_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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('vlt') + +test.lint(verilator_flags2=['--output-groups -1'], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_output_groups.py b/test_regress/t/t_output_groups.py new file mode 100755 index 000000000..37cfd95d2 --- /dev/null +++ b/test_regress/t/t_output_groups.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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('vlt_all') + +test.compile(verilator_flags2=["--output-groups", "2"]) + +test.execute() + +# Check that only vm_classes_*.cpp are to be compiled +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "Foo") +test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_1") +test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_1") +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_2") +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2") + +test.passes() diff --git a/test_regress/t/t_output_groups.v b/test_regress/t/t_output_groups.v new file mode 100644 index 000000000..7b44e03df --- /dev/null +++ b/test_regress/t/t_output_groups.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +virtual class Base; + pure virtual function int get_param; +endclass +class Foo#(int N = 17) extends Base; + function int get_param; + return N; + endfunction +endclass + +module t (/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + + localparam MAX = 128; + Base q[$]; + generate + // should result in many C++ files + genvar i; + for (i = 0; i < MAX; i++) + initial begin + Foo#(i) item = new; + q.push_back(item); + end + endgenerate + always @(posedge clk) begin + int sum = 0; + foreach (q[i]) + sum += q[i].get_param(); + if (sum != MAX * (MAX - 1) / 2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule