Emit implementations into separate files based on required headers.
This patch partitions AstCFuncs under an AstNodeModule based on which header files they require for their implementation, and emits them into separate files based on the distinct dependency sets. This helps with incremental recompilation of the output C++.
This commit is contained in:
parent
8bb77f86ec
commit
ab4063f098
2
Changes
2
Changes
|
|
@ -14,6 +14,8 @@ Verilator 4.211 devel
|
|||
**Minor:**
|
||||
|
||||
* Fix -G to treat simple integer literals as signed (#3060). [Anikin1610]
|
||||
* Output files are split based on the set of headers required
|
||||
in order to aid incremental compilation via ccache (#3071). [Geza Lore]
|
||||
|
||||
|
||||
Verilator 4.210 2021-07-07
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ class CUseVisitor final : public AstNVisitor {
|
|||
virtual void visit(AstClassRefDType* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (!m_impOnly) addNewUse(nodep, VUseType::INT_FWD_CLASS, nodep->classp()->name());
|
||||
// No class.h, it's inside the class package's h file
|
||||
addNewUse(nodep, VUseType::IMP_INCLUDE, nodep->classp()->classOrPackagep()->name());
|
||||
// Need to include extends() when we implement, but no need for pointers to know
|
||||
VL_RESTORER(m_impOnly);
|
||||
{
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ private:
|
|||
AstVarRef* m_wideTempRefp; // Variable that _WW macros should be setting
|
||||
int m_labelNum; // Next label number
|
||||
int m_splitSize; // # of cfunc nodes placed into output file
|
||||
int m_splitFilenum; // File number being created, 0 = primary
|
||||
bool m_inUC = false; // Inside an AstUCStmt or AstUCMath
|
||||
std::vector<AstChangeDet*> m_blkChangeDetVec; // All encountered changes in block
|
||||
|
||||
|
|
@ -133,15 +132,11 @@ public:
|
|||
VL_DEBUG_FUNC; // Declare debug()
|
||||
|
||||
// ACCESSORS
|
||||
int splitFilenumInc() {
|
||||
m_splitSize = 0;
|
||||
return m_splitFilenum++;
|
||||
}
|
||||
int splitSize() const { return m_splitSize; }
|
||||
void splitSizeInc(int count) { m_splitSize += count; }
|
||||
void splitSizeInc(AstNode* nodep) { splitSizeInc(EmitCBaseCounterVisitor(nodep).count()); }
|
||||
void splitSizeReset() { m_splitSize = 0; }
|
||||
bool splitNeeded() const {
|
||||
return v3Global.opt.outputSplit() && splitSize() >= v3Global.opt.outputSplit();
|
||||
return v3Global.opt.outputSplit() && m_splitSize >= v3Global.opt.outputSplit();
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
|
@ -1220,7 +1215,6 @@ public:
|
|||
m_wideTempRefp = nullptr;
|
||||
m_labelNum = 0;
|
||||
m_splitSize = 0;
|
||||
m_splitFilenum = 0;
|
||||
}
|
||||
EmitCFunc(AstNode* nodep, V3OutCFile* ofp, bool trackText = false)
|
||||
: EmitCFunc{} {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,126 @@
|
|||
|
||||
#include "V3Global.h"
|
||||
#include "V3EmitC.h"
|
||||
#include "V3Ast.h"
|
||||
#include "V3EmitCFunc.h"
|
||||
#include "V3String.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
//######################################################################
|
||||
// Visitor that gathers the headers required by an AstCFunc
|
||||
|
||||
class EmitCGatherDependencies final : AstNVisitor {
|
||||
// Ordered set, as it is used as a key in another map.
|
||||
std::set<string> m_dependencies; // Header names to be included in output C++ file
|
||||
|
||||
// METHODS
|
||||
void addSymsDependency() { m_dependencies.insert(EmitCBaseVisitor::symClassName()); }
|
||||
void addModDependency(const AstNodeModule* modp) {
|
||||
if (const AstClass* const classp = VN_CAST_CONST(modp, Class)) {
|
||||
m_dependencies.insert(EmitCBaseVisitor::prefixNameProtect(classp->classOrPackagep()));
|
||||
} else {
|
||||
m_dependencies.insert(EmitCBaseVisitor::prefixNameProtect(modp));
|
||||
}
|
||||
}
|
||||
void addDTypeDependency(const AstNodeDType* nodep) {
|
||||
if (const AstClassRefDType* const dtypep = VN_CAST_CONST(nodep, ClassRefDType)) {
|
||||
m_dependencies.insert(
|
||||
EmitCBaseVisitor::prefixNameProtect(dtypep->classp()->classOrPackagep()));
|
||||
}
|
||||
}
|
||||
void addSelfDependency(const string& selfPointer, AstNode* nodep) {
|
||||
if (selfPointer.empty()) {
|
||||
// No self pointer (e.g.: function locals, const pool values, loose static methods),
|
||||
// so no dependency
|
||||
} else if (VString::startsWith(selfPointer, "this")) {
|
||||
// Dereferencing 'this', we need the definition of this module, which is also the
|
||||
// module that contains the variable.
|
||||
addModDependency(EmitCParentModule::get(nodep));
|
||||
} else {
|
||||
// Must be an absolute reference
|
||||
UASSERT_OBJ(selfPointer.find("vlSymsp") != string::npos, nodep,
|
||||
"Unknown self pointer: '" << selfPointer << "'");
|
||||
// Dereferencing vlSymsp, so we need it's definition...
|
||||
m_dependencies.insert(EmitCBaseVisitor::symClassName());
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstCCall* nodep) override {
|
||||
addSelfDependency(nodep->selfPointer(), nodep->funcp());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstCNew* nodep) override {
|
||||
addDTypeDependency(nodep->dtypep());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstCMethodCall* nodep) override {
|
||||
addDTypeDependency(nodep->fromp()->dtypep());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstNewCopy* nodep) override {
|
||||
addDTypeDependency(nodep->dtypep());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstMemberSel* nodep) override {
|
||||
addDTypeDependency(nodep->fromp()->dtypep());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstNodeVarRef* nodep) override {
|
||||
addSelfDependency(nodep->selfPointer(), nodep->varp());
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstCoverDecl* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstCoverInc* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstDumpCtl* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstScopeName* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstPrintTimeScale* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstTimeFormat* nodep) override {
|
||||
addSymsDependency();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstNodeSimpleText* nodep) override {
|
||||
if (nodep->text().find("vlSymsp") != string::npos) {
|
||||
m_dependencies.insert(EmitCBaseVisitor::symClassName());
|
||||
}
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
||||
|
||||
// CONSTRUCTOR
|
||||
explicit EmitCGatherDependencies(AstCFunc* cfuncp) {
|
||||
// Strictly speaking, for loose methods, we could get away with just a forward
|
||||
// declaration of the receiver class, but their body very likely includes at least one
|
||||
// relative reference, so we are probably not loosing much.
|
||||
addModDependency(EmitCParentModule::get(cfuncp));
|
||||
iterate(cfuncp);
|
||||
}
|
||||
|
||||
public:
|
||||
static const std::set<std::string> gather(AstCFunc* cfuncp) {
|
||||
EmitCGatherDependencies visitor{cfuncp};
|
||||
return std::move(visitor.m_dependencies);
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Internal EmitC implementation
|
||||
|
|
@ -31,11 +147,15 @@ class EmitCImp final : EmitCFunc {
|
|||
// MEMBERS
|
||||
const AstNodeModule* const m_fileModp; // Files names/headers constructed using this module
|
||||
const bool m_slow; // Creating __Slow file
|
||||
const std::set<string>* m_requiredHeadersp; // Header files required by output file
|
||||
std::string m_subFileName; // substring added to output filenames
|
||||
V3UniqueNames m_uniqueNames; // For generating unique file names
|
||||
|
||||
// METHODS
|
||||
void openNextOutputFile() {
|
||||
void openNextOutputFile(const std::set<string>& headers, const string& subFileName) {
|
||||
UASSERT(!m_ofp, "Output file already open");
|
||||
|
||||
splitSizeReset(); // Reset file size tracking
|
||||
m_lazyDecls.reset(); // Need to emit new lazy declarations
|
||||
|
||||
if (v3Global.opt.lintOnly()) {
|
||||
|
|
@ -46,7 +166,10 @@ class EmitCImp final : EmitCFunc {
|
|||
m_ofp = new V3OutCFile(filename);
|
||||
} else {
|
||||
string filename = v3Global.opt.makeDir() + "/" + prefixNameProtect(m_fileModp);
|
||||
if (const int filenum = splitFilenumInc()) filename += "__" + cvtToStr(filenum);
|
||||
if (!subFileName.empty()) {
|
||||
filename += "__" + subFileName;
|
||||
filename = m_uniqueNames.get(filename);
|
||||
}
|
||||
if (m_slow) filename += "__Slow";
|
||||
filename += ".cpp";
|
||||
newCFile(filename, /* slow: */ m_slow, /* source: */ true);
|
||||
|
|
@ -58,21 +181,26 @@ class EmitCImp final : EmitCFunc {
|
|||
puts("// See " + topClassName() + ".h for the primary calling header\n");
|
||||
|
||||
// Include files
|
||||
puts("\n#include \"verilated_heavy.h\"\n");
|
||||
if (v3Global.dpi()) puts("#include \"verilated_dpi.h\"\n");
|
||||
puts("\n");
|
||||
puts("#include \"" + prefixNameProtect(m_fileModp) + ".h\"\n");
|
||||
puts("#include \"" + symClassName() + ".h\"\n");
|
||||
|
||||
if (v3Global.dpi()) {
|
||||
puts("\n");
|
||||
puts("#include \"verilated_dpi.h\"\n");
|
||||
}
|
||||
|
||||
emitModCUse(m_fileModp, VUseType::IMP_INCLUDE);
|
||||
emitModCUse(m_fileModp, VUseType::IMP_FWD_CLASS);
|
||||
for (const string& name : headers) puts("#include \"" + name + ".h\"\n");
|
||||
|
||||
emitTextSection(m_modp, AstType::atScImpHdr);
|
||||
}
|
||||
|
||||
void emitStaticVarDefns(const AstNodeModule* modp) {
|
||||
// Emit static variable definitions
|
||||
const string modName = prefixNameProtect(modp);
|
||||
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
|
||||
if (varp->isStatic()) {
|
||||
puts(varp->vlArgType(true, false, false, modName));
|
||||
puts(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void emitParamDefns(const AstNodeModule* modp) {
|
||||
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
|
||||
|
|
@ -168,8 +296,9 @@ class EmitCImp final : EmitCFunc {
|
|||
void emitCoverageImp() {
|
||||
if (v3Global.opt.coverage()) {
|
||||
puts("\n// Coverage\n");
|
||||
// Rather than putting out VL_COVER_INSERT calls directly, we do it via this function
|
||||
// This gets around gcc slowness constructing all of the template arguments.
|
||||
// Rather than putting out VL_COVER_INSERT calls directly, we do it via this
|
||||
// function. This gets around gcc slowness constructing all of the template
|
||||
// arguments.
|
||||
puts("void " + prefixNameProtect(m_modp) + "::__vlCoverInsert(");
|
||||
puts(v3Global.opt.threads() ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
|
|
@ -289,19 +418,23 @@ class EmitCImp final : EmitCFunc {
|
|||
}
|
||||
}
|
||||
}
|
||||
void emitAll(const AstNodeModule* modp) {
|
||||
// Predicate to check if we actually need to emit anything into the common implementation file.
|
||||
// Used to avoid creating empty output files.
|
||||
bool hasCommonImp(const AstNodeModule* modp) const {
|
||||
// Nothing to emit if no module!
|
||||
if (!modp) return false;
|
||||
// We always need the slow file
|
||||
if (m_slow) return true;
|
||||
// The fast file is only required when we have ScImp nodes
|
||||
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (VN_IS(nodep, ScImp)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Actually emit common implementation contents for given AstNodeModule
|
||||
void doCommonImp(const AstNodeModule* modp) {
|
||||
if (m_slow) {
|
||||
// Emit static variable definitions
|
||||
const string modName = prefixNameProtect(modp);
|
||||
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
|
||||
if (varp->isStatic()) {
|
||||
puts(varp->vlArgType(true, false, false, modName));
|
||||
puts(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitStaticVarDefns(modp);
|
||||
if (!VN_IS(modp, Class)) {
|
||||
emitParamDefns(modp);
|
||||
emitCtorImp(modp);
|
||||
|
|
@ -311,30 +444,86 @@ class EmitCImp final : EmitCFunc {
|
|||
emitSavableImp(modp);
|
||||
emitCoverageImp();
|
||||
} else {
|
||||
// From `systemc_implementation
|
||||
emitTextSection(modp, AstType::atScImp);
|
||||
}
|
||||
}
|
||||
void emitCommonImp(const AstNodeModule* modp) {
|
||||
const AstClass* const classp
|
||||
= VN_IS(modp, ClassPackage) ? VN_CAST_CONST(modp, ClassPackage)->classp() : nullptr;
|
||||
|
||||
// Emit all AstCFunc
|
||||
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) iterate(funcp);
|
||||
if (hasCommonImp(modp) || hasCommonImp(classp)) {
|
||||
std::set<string> headers;
|
||||
headers.insert(prefixNameProtect(m_fileModp));
|
||||
headers.insert(symClassName());
|
||||
|
||||
openNextOutputFile(headers, "");
|
||||
|
||||
doCommonImp(modp);
|
||||
if (classp) {
|
||||
VL_RESTORER(m_modp);
|
||||
m_modp = classp;
|
||||
doCommonImp(classp);
|
||||
}
|
||||
|
||||
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
||||
}
|
||||
}
|
||||
void emitCFuncImp(const AstNodeModule* modp) {
|
||||
// Partition functions based on which module definitions they require, by building a
|
||||
// map from "AstNodeModules whose definitions are required" -> "functions that need
|
||||
// them"
|
||||
std::map<const std::set<string>, std::vector<AstCFunc*>> depSet2funcps;
|
||||
|
||||
const auto gather = [this, &depSet2funcps](const AstNodeModule* modp) {
|
||||
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) {
|
||||
// TRACE_* and DPI handled elsewhere
|
||||
if (funcp->isTrace()) continue;
|
||||
if (funcp->dpiImportPrototype()) continue;
|
||||
if (funcp->dpiExportDispatcher()) continue;
|
||||
if (funcp->slow() != m_slow) continue;
|
||||
const auto& depSet = EmitCGatherDependencies::gather(funcp);
|
||||
depSet2funcps[depSet].push_back(funcp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gather(modp);
|
||||
if (const AstClassPackage* const packagep = VN_CAST_CONST(modp, ClassPackage)) {
|
||||
gather(packagep->classp());
|
||||
}
|
||||
|
||||
// Emit all functions in each dependency set into separate files
|
||||
for (const auto& pair : depSet2funcps) {
|
||||
m_requiredHeadersp = &pair.first;
|
||||
// Compute the hash of the dependencies, so we can add it to the filenames to
|
||||
// disambiguate them
|
||||
V3Hash hash;
|
||||
for (const string& name : *m_requiredHeadersp) { hash += name; }
|
||||
m_subFileName = "DepSet_" + cvtToHex(hash.value());
|
||||
// Open output file
|
||||
openNextOutputFile(*m_requiredHeadersp, m_subFileName);
|
||||
// Emit functions in this dependency set
|
||||
for (AstCFunc* const funcp : pair.second) {
|
||||
VL_RESTORER(m_modp);
|
||||
m_modp = EmitCParentModule::get(funcp);
|
||||
iterate(funcp);
|
||||
}
|
||||
// Close output file
|
||||
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstCFunc* nodep) override {
|
||||
// TRACE_* and DPI handled elsewhere
|
||||
if (nodep->isTrace()) return;
|
||||
if (nodep->dpiImportPrototype()) return;
|
||||
if (nodep->dpiExportDispatcher()) return;
|
||||
if (nodep->slow() != m_slow) return;
|
||||
|
||||
if (splitNeeded()) {
|
||||
// Splitting file, so using parallel build.
|
||||
v3Global.useParallelBuild(true);
|
||||
// Close old file
|
||||
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
||||
// Open a new file
|
||||
openNextOutputFile();
|
||||
openNextOutputFile(*m_requiredHeadersp, m_subFileName);
|
||||
}
|
||||
|
||||
EmitCFunc::visit(nodep);
|
||||
|
|
@ -347,20 +536,16 @@ class EmitCImp final : EmitCFunc {
|
|||
|
||||
m_modp = modp;
|
||||
|
||||
openNextOutputFile();
|
||||
// Emit implementation of this module, if this is an AstClassPackage, then put the
|
||||
// corresponding AstClass implementation in the same file as often optimziations are
|
||||
// possible when both are seen by the compiler
|
||||
// TODO: is the above comment still true?
|
||||
|
||||
emitAll(modp);
|
||||
// Emit implementations of common parts
|
||||
emitCommonImp(modp);
|
||||
|
||||
if (const AstClassPackage* const packagep = VN_CAST_CONST(modp, ClassPackage)) {
|
||||
// Put the non-static class implementation in same C++ files as
|
||||
// often optimizations are possible when both are seen by the
|
||||
// compiler together
|
||||
m_modp = packagep->classp();
|
||||
emitAll(packagep->classp());
|
||||
}
|
||||
|
||||
// Close output file
|
||||
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
||||
// Emit implementations of all AstCFunc
|
||||
emitCFuncImp(modp);
|
||||
}
|
||||
virtual ~EmitCImp() override = default;
|
||||
|
||||
|
|
@ -380,21 +565,24 @@ class EmitCTrace final : EmitCFunc {
|
|||
// MEMBERS
|
||||
const bool m_slow; // Making slow file
|
||||
int m_enumNum = 0; // Enumeration number (whole netlist)
|
||||
V3UniqueNames m_uniqueNames; // For generating unique file names
|
||||
|
||||
// METHODS
|
||||
void newOutCFile(int filenum) {
|
||||
void openNextOutputFile() {
|
||||
UASSERT(!m_ofp, "Output file already open");
|
||||
|
||||
splitSizeReset(); // Reset file size tracking
|
||||
m_lazyDecls.reset(); // Need to emit new lazy declarations
|
||||
|
||||
string filename
|
||||
= (v3Global.opt.makeDir() + "/" + topClassName() + "_" + protect("_Trace"));
|
||||
if (filenum) filename += "__" + cvtToStr(filenum);
|
||||
filename += (m_slow ? "__Slow" : "");
|
||||
filename = m_uniqueNames.get(filename);
|
||||
if (m_slow) filename += "__Slow";
|
||||
filename += ".cpp";
|
||||
|
||||
AstCFile* cfilep = newCFile(filename, m_slow, true /*source*/);
|
||||
cfilep->support(true);
|
||||
|
||||
if (m_ofp) v3fatalSrc("Previous file not closed");
|
||||
if (optSystemC()) {
|
||||
m_ofp = new V3OutScFile(filename);
|
||||
} else {
|
||||
|
|
@ -647,7 +835,7 @@ class EmitCTrace final : EmitCFunc {
|
|||
// Close old file
|
||||
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
||||
// Open a new file
|
||||
newOutCFile(splitFilenumInc());
|
||||
openNextOutputFile();
|
||||
}
|
||||
|
||||
EmitCFunc::visit(nodep);
|
||||
|
|
@ -678,7 +866,7 @@ class EmitCTrace final : EmitCFunc {
|
|||
: m_slow{slow} {
|
||||
m_modp = modp;
|
||||
// Open output file
|
||||
newOutCFile(splitFilenumInc());
|
||||
openNextOutputFile();
|
||||
// Emit functions
|
||||
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) { iterate(funcp); }
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@
|
|||
#include "V3Global.h"
|
||||
#include "V3EmitC.h"
|
||||
#include "V3EmitCFunc.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
class EmitCModel final : public EmitCFunc {
|
||||
V3UniqueNames m_uniqueNames; // For generating unique file names
|
||||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC;
|
||||
|
|
@ -584,11 +586,13 @@ class EmitCModel final : public EmitCFunc {
|
|||
}
|
||||
|
||||
if (!m_ofp) {
|
||||
const string filename = v3Global.opt.makeDir() + "/" + topClassName()
|
||||
+ "__Dpi_Export_" + cvtToStr(splitFilenumInc()) + ".cpp";
|
||||
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};
|
||||
splitSizeReset(); // Reset file size tracking
|
||||
m_lazyDecls.reset();
|
||||
m_ofp->putsHeader();
|
||||
puts(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Basic data structure to keep names unique
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2005-2021 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_V3UNIQUENAMES_H_
|
||||
#define VERILATOR_V3UNIQUENAMES_H_
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class V3UniqueNames final {
|
||||
std::unordered_map<std::string, unsigned> m_multiplicity; // Suffix number for given key
|
||||
|
||||
public:
|
||||
// Return argument, appended with a unique suffix each time we are called with the same
|
||||
// argument.
|
||||
std::string get(const std::string& name) {
|
||||
const unsigned num = m_multiplicity.emplace(name, 0).first->second++;
|
||||
return name + "__" + cvtToStr(num);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
@ -2371,6 +2371,33 @@ sub tries {
|
|||
return 2;
|
||||
}
|
||||
|
||||
sub glob_all {
|
||||
my $self = (ref $_[0]? shift : $Self);
|
||||
my $pattern = shift;
|
||||
|
||||
return glob($pattern);
|
||||
}
|
||||
|
||||
sub glob_one {
|
||||
my $self = (ref $_[0]? shift : $Self);
|
||||
my $pattern = shift;
|
||||
return if $self->errors || $self->skips || $self->unsupporteds;
|
||||
|
||||
my @files = glob($pattern);
|
||||
my $n = scalar @files;
|
||||
if ($n == 0) {
|
||||
$self->error("glob_one: pattern '$pattern' does not match any files\n");
|
||||
} elsif ($n != 1) {
|
||||
my $msg = "glob_one: pattern '$pattern' matches multiple files:\n";
|
||||
foreach my $file (@files) {
|
||||
$msg .= $file."\n";
|
||||
}
|
||||
$self->error($msg);
|
||||
} else {
|
||||
return $files[0];
|
||||
}
|
||||
}
|
||||
|
||||
sub file_grep_not {
|
||||
my $self = (ref $_[0]? shift : $Self);
|
||||
my $filename = shift;
|
||||
|
|
@ -2402,6 +2429,30 @@ sub file_grep {
|
|||
}
|
||||
}
|
||||
|
||||
sub file_grep_any {
|
||||
my $self = $Self;
|
||||
my @filenames = @{$_[0]}; shift;
|
||||
my $regexp = shift;
|
||||
my $expvalue = shift;
|
||||
return if $self->errors || $self->skips || $self->unsupporteds;
|
||||
|
||||
foreach my $filename (@filenames) {
|
||||
my $contents = $self->file_contents($filename);
|
||||
return if ($contents eq "_Already_Errored_");
|
||||
if ($contents =~ /$regexp/) {
|
||||
if ($expvalue && $expvalue ne $1) {
|
||||
$self->error("file_grep: $filename: Got='$1' Expected='$expvalue' in regexp: $regexp\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
my $msg = "file_grep_any: Regexp '$regexp' not found in any of the following files:\n";
|
||||
foreach my $filename (@filenames) {
|
||||
$msg .= $filename."\n";
|
||||
}
|
||||
$self->error($msg);
|
||||
}
|
||||
|
||||
my %_File_Contents_Cache;
|
||||
|
||||
sub file_contents {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ file_grep($Self->{stats}, qr/Optimizations, Split always\s+(\d+)/i, 0);
|
|||
# Here we should see some dly vars since reorder is disabled.
|
||||
# (Whereas our twin test, t_alw_reorder, should see no dly vars
|
||||
# since it enables the reorder step.)
|
||||
file_grep("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/dly__t__DOT__v1/i);
|
||||
file_grep("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/dly__t__DOT__v2/i);
|
||||
my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root*.cpp");
|
||||
file_grep_any(\@files, qr/dly__t__DOT__v1/i);
|
||||
file_grep_any(\@files, qr/dly__t__DOT__v2/i);
|
||||
|
||||
execute(
|
||||
check_finished=>1,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ file_grep($Self->{stats}, qr/Optimizations, Split always\s+(\d+)/i, 0);
|
|||
# Important: if reorder succeeded, we should see no dly vars.
|
||||
# Equally important: twin test t_alw_noreorder should see dly vars,
|
||||
# is identical to this test except for disabling the reorder step.
|
||||
foreach my $file ("$Self->{obj_dir}/$Self->{VM_PREFIX}.cpp",
|
||||
"$Self->{obj_dir}/$Self->{VM_PREFIX}.h") {
|
||||
foreach my $file (
|
||||
glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.h"),
|
||||
glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")
|
||||
) {
|
||||
file_grep_not($file, qr/dly__t__DOT__v1/i);
|
||||
file_grep_not($file, qr/dly__t__DOT__v2/i);
|
||||
file_grep_not($file, qr/dly__t__DOT__v3/i);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ compile();
|
|||
if ($Self->{vlt_all}) {
|
||||
# The word 'this' (but only the whole word 'this' should have been replaced
|
||||
# in the contents.
|
||||
my $file = "$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp";
|
||||
my $file = glob_one("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__0.cpp");
|
||||
my $text = file_contents($file);
|
||||
error("$file has 'this->clk'") if ($text =~ m/\bthis->clk\b/);
|
||||
error("$file does not have 'xthis'") if ($text !~ m/\bxthis\b/);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ execute(
|
|||
check_finished => 1,
|
||||
);
|
||||
|
||||
file_grep("$Self->{obj_dir}/Vt_flag_comp_limit_parens___024root__Slow.cpp", qr/Vdeeptemp/x);
|
||||
file_grep(glob_one("$Self->{obj_dir}/Vt_flag_comp_limit_parens___024root__DepSet_*__0__Slow.cpp"), qr/Vdeeptemp/x);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ while (1) {
|
|||
sub check_no_splits {
|
||||
foreach my $file (glob("$Self->{obj_dir}/*.cpp")) {
|
||||
$file =~ s/__024root//;
|
||||
if ($file =~ qr/__\d/) {
|
||||
if ($file =~ qr/__[1-9]/) {
|
||||
error("Split file found: $file");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ execute(
|
|||
check_finished => 1,
|
||||
);
|
||||
|
||||
file_grep("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__Slow.cpp", qr/VL_RAND_RESET/);
|
||||
file_grep(glob_one("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__0__Slow.cpp"), qr/VL_RAND_RESET/);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -20,14 +20,16 @@ execute(
|
|||
|
||||
# We expect all loops should be unrolled by verilator,
|
||||
# none of the loop variables should exist in the output:
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/index_/);
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__Slow.cpp", qr/index_/);
|
||||
for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")) {
|
||||
file_grep_not($file, qr/index_/);
|
||||
}
|
||||
|
||||
# Further, we expect that all logic within the loop should
|
||||
# have been evaluated inside the compiler. So there should be
|
||||
# no references to 'sum' in the .cpp.
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/sum/);
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__Slow.cpp", qr/sum/);
|
||||
for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")) {
|
||||
file_grep_not($file, qr/sum/);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -23,17 +23,18 @@ sub checkRelativeRefs {
|
|||
my ($mod, $expect_relative) = @_;
|
||||
my $found_relative = 0;
|
||||
|
||||
my $file = "$Self->{obj_dir}/V$Self->{name}_${mod}.cpp";
|
||||
my $text = file_contents($file);
|
||||
foreach my $file (glob_all("$Self->{obj_dir}/V$Self->{name}_${mod}*.cpp")) {
|
||||
my $text = file_contents($file);
|
||||
|
||||
if ($text =~ m/this->/ || $text =~ m/vlSelf->/) {
|
||||
$found_relative = 1;
|
||||
}
|
||||
if ($text =~ m/this->/ || $text =~ m/vlSelf->/) {
|
||||
$found_relative = 1;
|
||||
}
|
||||
|
||||
if ($found_relative != $expect_relative) {
|
||||
error("$file " .
|
||||
($found_relative ? "has" : "does not have") .
|
||||
" relative variable references.");
|
||||
if ($found_relative != $expect_relative) {
|
||||
error("$file " .
|
||||
($found_relative ? "has" : "does not have") .
|
||||
" relative variable references.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ execute(
|
|||
check_finished => 1,
|
||||
);
|
||||
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/rstn_r/);
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__Slow.cpp", qr/rstn_r/);
|
||||
for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root*.cpp")) {
|
||||
file_grep_not($file, qr/rstn_r/);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ execute(
|
|||
check_finished => 1,
|
||||
);
|
||||
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root.cpp", qr/rstn_r/);
|
||||
file_grep_not("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__Slow.cpp", qr/rstn_r/);
|
||||
for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root*.cpp")) {
|
||||
file_grep_not($file, qr/rstn_r/);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ execute(
|
|||
);
|
||||
|
||||
if ($Self->{vlt_all}) {
|
||||
file_grep ("$Self->{obj_dir}/V$Self->{name}__Trace__Slow.cpp", qr/c_trace_on\"/x);
|
||||
file_grep_not ("$Self->{obj_dir}/V$Self->{name}__Trace__Slow.cpp", qr/_trace_off\"/x);
|
||||
file_grep ("$Self->{obj_dir}/V$Self->{name}__Trace__0__Slow.cpp", qr/c_trace_on\"/x);
|
||||
file_grep_not ("$Self->{obj_dir}/V$Self->{name}__Trace__0__Slow.cpp", qr/_trace_off\"/x);
|
||||
file_grep ("$Self->{obj_dir}/simx.vcd", qr/\$enddefinitions/x);
|
||||
file_grep_not ("$Self->{obj_dir}/simx.vcd", qr/inside_sub/x);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue