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:
parent
1a31aa5d62
commit
0547108e3f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
|
|
491
src/V3EmitMk.cpp
491
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<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)) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
%Error: --output-groups must be >= 0: -1
|
||||
%Error: Exiting due to
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
Loading…
Reference in New Issue