Add `-output-groups` to build with concatenated .cpp files (#5257)

Signed-off-by: Bartłomiej Chmiel <bchmiel@antmicro.com>
Signed-off-by: Ryszard Rozak <rrozak@antmicro.com>
Signed-off-by: Arkadiusz Kozdra <akozdra@antmicro.com>
Co-authored-by: Mariusz Glebocki <mglebocki@antmicro.com>
Co-authored-by: Arkadiusz Kozdra <akozdra@antmicro.com>
Co-authored-by: Bartłomiej Chmiel <bachm44@gmail.com>
Co-authored-by: Wilson Snyder <wsnyder@wsnyder.org>
Co-authored-by: Ryszard Rozak <rrozak@antmicro.com>
This commit is contained in:
Mariusz Glebocki 2024-10-01 03:42:36 +02:00 committed by GitHub
parent 1a31aa5d62
commit 0547108e3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 840 additions and 78 deletions

View File

@ -405,6 +405,7 @@ detailed descriptions of these arguments.
-O3 High-performance optimizations
-O<optimization-letter> Selectable optimizations
-o <executable> Name of final executable
--output-groups <numfiles> Group .cpp files into larger ones
--output-split <statements> Split .cpp files into pieces
--output-split-cfuncs <statements> Split model functions
--output-split-ctrace <statements> Split tracing functions

View File

@ -958,6 +958,22 @@ Summary:
delayed assignments. This option should only be used when suggested by
the developers.
.. option:: --output-groups <numfiles>
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 <statements>
Enables splitting the output .cpp files into multiple outputs. When a

View File

@ -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; }

View File

@ -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(); }

View File

@ -30,6 +30,9 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// Const pool emitter
class EmitCConstPool final : public EmitCConstInit {
// TYPES
using OutCFilePair = std::pair<V3OutCFile*, AstCFile*>;
// 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

View File

@ -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);
}

View File

@ -18,6 +18,7 @@
#include "V3EmitC.h"
#include "V3EmitCConstInit.h"
#include "V3File.h"
#include "V3UniqueNames.h"
#include <algorithm>
@ -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;

View File

@ -163,7 +163,7 @@ class EmitCImp final : EmitCFunc {
// METHODS
void openNextOutputFile(const std::set<string>& 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};
}
}
}

View File

@ -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);
}
};

View File

@ -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) {

View File

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

View File

@ -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<std::string> 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<FilenameWithScore> 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<FilenameWithScore>
m_inputFiles; // List of filenames from initial AstCFile list
std::vector<FileOrConcatenatedFilesList>
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<WorkList> m_workLists; // Lists of small enough files
std::unique_ptr<std::ofstream> m_logp; // Dump file
std::vector<WorkList*> m_concatenableListsByDescSize; // Lists sorted by size, descending
EmitGroup(std::vector<FilenameWithScore> 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<uint64_t> 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<int64_t>(topScore + 1, MAX_INTERVALS_NUM);
struct Interval final {
uint64_t m_lowerBound = 0;
int m_size = 0;
};
std::vector<Interval> 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<int>(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<std::ofstream>{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<FileOrConcatenatedFilesList>
singleConcatenatedFilesList(std::vector<FilenameWithScore> 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<FileOrConcatenatedFilesList> vmClassesSlowList;
std::vector<FileOrConcatenatedFilesList> vmClassesFastList;
if (v3Global.opt.outputGroups() > 0) {
std::vector<FilenameWithScore> slowFiles;
std::vector<FilenameWithScore> 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<FilenameWithScore>& 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<FileOrConcatenatedFilesList>& 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)) {

View File

@ -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,

View File

@ -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; }

View File

@ -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()

View File

@ -0,0 +1,2 @@
%Error: --output-groups must be >= 0: -1
%Error: Exiting due to

View File

@ -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()

View File

@ -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()

View File

@ -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