Add V3VariableOrder pass

A separate V3VariableOrder pass is now used to order module variables
before Emit. All variables are now ordered together, without
consideration for whether they are ports, signals form the design, or
additional internal variables added by Verilator (which used to be
ordered and emitted as separate groups in Emit). For single threaded
models, this is performance neutral. For multi-threaded models, the
MTask affinity based sorting was slightly modified, so variables with no
MTask affinity are emitted last, otherwise the MTask affinity sets are
sorted using the TSP sorter as before, but again, ports, signals, and
internal variables are not differentiated. This yields a 2%+ speedup for
the multithreaded model on OpenTitan.
This commit is contained in:
Geza Lore 2021-06-29 17:57:07 +01:00
parent 8ecdc85cf7
commit 17cc452f79
16 changed files with 396 additions and 333 deletions

View File

@ -254,6 +254,7 @@ RAW_OBJS = \
V3Undriven.o \
V3Unknown.o \
V3Unroll.o \
V3VariableOrder.o \
V3Waiver.o \
V3Width.o \
V3WidthSel.o \

View File

@ -33,7 +33,7 @@ template <class T_Node, class T_Data, int T_UserN> class AstUserAllocatorBase VL
private:
std::vector<T_Data*> m_allocated;
inline T_Data* getUserp(T_Node* nodep) const {
inline T_Data* getUserp(const T_Node* nodep) const {
// This simplifies statically as T_UserN is constant. In C++17, use 'if constexpr'.
if (T_UserN == 1) {
const VNUser user = nodep->user1u();
@ -100,6 +100,13 @@ public:
}
return *userp;
}
// Get a reference to the user data
T_Data& operator()(const T_Node* nodep) {
T_Data* userp = getUserp(nodep);
UASSERT_OBJ(userp, nodep, "Missing User data on const AstNode");
return *userp;
}
};
// User pointer allocator classes. T_Node is the type of node the allocator should be applied to

View File

@ -123,7 +123,7 @@ class EmitCImp final : EmitCFunc {
}
}
}
void emitParams(AstNodeModule* modp, bool init, bool* firstp, string& sectionr) {
void emitParams(AstNodeModule* modp, bool init, string& sectionr) {
bool anyi = false;
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* varp = VN_CAST(nodep, Var)) {
@ -207,6 +207,99 @@ class EmitCImp final : EmitCFunc {
emitCFuncDecl(funcp, modp);
}
}
void emitVarDecls(const AstNodeModule* modp) {
// Output a list of variable declarations
std::vector<const AstVar*> varList;
bool lastAnon = false; // initial value is not important, but is used
const auto emitCurrentList = [this, &varList, &lastAnon]() {
if (varList.empty()) return;
if (lastAnon) { // Output as anons
const int anonMembers = varList.size();
const int lim = v3Global.opt.compLimitMembers();
int anonL3s = 1;
int anonL2s = 1;
int anonL1s = 1;
if (anonMembers > (lim * lim * lim)) {
anonL3s = (anonMembers + (lim * lim * lim) - 1) / (lim * lim * lim);
anonL2s = lim;
anonL1s = lim;
} else if (anonMembers > (lim * lim)) {
anonL2s = (anonMembers + (lim * lim) - 1) / (lim * lim);
anonL1s = lim;
} else if (anonMembers > lim) {
anonL1s = (anonMembers + lim - 1) / lim;
}
if (anonL1s != 1)
puts("// Anonymous structures to workaround compiler member-count bugs\n");
auto it = varList.cbegin();
for (int l3 = 0; l3 < anonL3s && it != varList.cend(); ++l3) {
if (anonL3s != 1) puts("struct {\n");
for (int l2 = 0; l2 < anonL2s && it != varList.cend(); ++l2) {
if (anonL2s != 1) puts("struct {\n");
for (int l1 = 0; l1 < anonL1s && it != varList.cend(); ++l1) {
if (anonL1s != 1) puts("struct {\n");
for (int l0 = 0; l0 < lim && it != varList.cend(); ++l0) {
emitVarDecl(*it);
++it;
}
if (anonL1s != 1) puts("};\n");
}
if (anonL2s != 1) puts("};\n");
}
if (anonL3s != 1) puts("};\n");
}
// Leftovers, just in case off by one error somewhere above
for (; it != varList.cend(); ++it) emitVarDecl(*it);
} else { // Output as nonanons
for (const auto& pair : varList) emitVarDecl(pair);
}
varList.clear();
};
// Emit variables in consecutive anon and non-anon batches
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()
|| (varp->isParam() && !VN_IS(varp->valuep(), Const))) {
const bool anon = isAnonOk(varp);
if (anon != lastAnon) emitCurrentList();
lastAnon = anon;
varList.emplace_back(varp);
}
}
}
// Emit final batch
emitCurrentList();
}
void emitVarCtors(const AstNodeModule* modp) {
ofp()->indentInc();
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
const AstBasicDType* const dtypep = VN_CAST(varp->dtypeSkipRefp(), BasicDType);
if (!dtypep) continue;
if (varp->isIO() && varp->isSc()) {
puts(", ");
puts(varp->nameProtect());
puts("(");
putsQuoted(varp->nameProtect());
puts(")\n");
}
if (dtypep->keyword().isMTaskState()) {
puts(", ");
puts(varp->nameProtect());
puts("(");
iterate(varp->valuep());
puts(")\n");
}
}
}
ofp()->indentDec();
}
// Medium level
void emitCtorImp(AstNodeModule* modp);
@ -248,9 +341,8 @@ void EmitCImp::emitCoverageDecl(AstNodeModule*) {
void EmitCImp::emitCtorImp(AstNodeModule* modp) {
puts("\n");
bool first = true;
string section;
emitParams(modp, true, &first, section /*ref*/);
emitParams(modp, true, section);
const string modName = prefixNameProtect(modp);
@ -264,9 +356,8 @@ void EmitCImp::emitCtorImp(AstNodeModule* modp) {
} else {
puts(modName + "::" + modName + "(const char* _vcname__)\n");
puts(" : VerilatedModule(_vcname__)\n");
first = false; // printed the first ':'
}
emitVarCtors(&first);
emitVarCtors(modp);
puts(" {\n");
@ -500,15 +591,8 @@ void EmitCImp::emitInt(AstNodeModule* modp) {
emitTypedefs(modp->stmtsp());
string section;
section = "\n// PORTS\n";
emitVarList(modp->stmtsp(), EVL_CLASS_IO, "", section /*ref*/);
section = "\n// LOCAL SIGNALS\n";
emitVarList(modp->stmtsp(), EVL_CLASS_SIG, "", section /*ref*/);
section = "\n// LOCAL VARIABLES\n";
emitVarList(modp->stmtsp(), EVL_CLASS_TEMP, "", section /*ref*/);
puts("\n// DESIGN SPECIFIC STATE\n");
emitVarDecls(modp);
puts("\n// INTERNAL VARIABLES\n");
if (!VN_IS(modp, Class)) { // Avoid clang unused error (& don't want in every object)
@ -518,12 +602,10 @@ void EmitCImp::emitInt(AstNodeModule* modp) {
ofp()->putsPrivate(false); // public:
emitCoverageDecl(modp); // may flip public/private
section = "\n// PARAMETERS\n";
ofp()->putsPrivate(false); // public:
emitVarList(modp->stmtsp(), EVL_CLASS_PAR, "",
section /*ref*/); // Only those that are non-CONST
bool first = true;
emitParams(modp, false, &first, section /*ref*/);
{
string section = "\n// PARAMETERS\n";
emitParams(modp, false, section);
}
if (!VN_IS(modp, Class)) {
puts("\n// CONSTRUCTORS\n");
@ -588,8 +670,16 @@ void EmitCImp::emitImpTop() {
void EmitCImp::emitImp(AstNodeModule* modp) {
puts("\n//==========\n");
if (m_slow) {
string section;
emitVarList(modp->stmtsp(), EVL_CLASS_ALL, prefixNameProtect(modp), section /*ref*/);
// Emit static variable definitions
const string prefix = 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, prefix));
puts(";\n");
}
}
}
if (!VN_IS(modp, Class)) emitCtorImp(modp);
if (!VN_IS(modp, Class)) emitConfigureImp(modp);
if (!VN_IS(modp, Class)) emitDestructorImp(modp);

View File

@ -114,7 +114,7 @@ void EmitCBaseVisitor::emitCFuncDecl(const AstCFunc* funcp, const AstNodeModule*
if (!funcp->ifdef().empty()) puts("#endif // " + funcp->ifdef() + "\n");
}
void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, const string& prefixIfImp, bool asRef) {
void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, bool asRef) {
const AstBasicDType* const basicp = nodep->basicp();
bool refNeedParens = VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType);
@ -199,7 +199,7 @@ void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, const string& prefixIfIm
&& name.substr(name.size() - suffix.size()) == suffix;
if (beStatic) puts("static VL_THREAD_LOCAL ");
}
puts(nodep->vlArgType(true, false, false, prefixIfImp, asRef));
puts(nodep->vlArgType(true, false, false, "", asRef));
puts(";\n");
}
}

View File

@ -75,11 +75,18 @@ public:
return modp == v3Global.rootp()->constPoolp()->modp();
}
static bool isAnonOk(const AstVar* varp) {
return v3Global.opt.compLimitMembers() != 0 // Enabled
&& !varp->isStatic() // Not a static variable
&& !varp->isSc() // Aggregates can't be anon
&& (varp->basicp() && !varp->basicp()->isOpaque()); // Aggregates can't be anon
}
static AstCFile* newCFile(const string& filename, bool slow, bool source);
string cFuncArgs(const AstCFunc* nodep);
void emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp, bool withScope);
void emitCFuncDecl(const AstCFunc* funcp, const AstNodeModule* modp, bool cLinkage = false);
void emitVarDecl(const AstVar* nodep, const string& prefixIfImp, bool asRef = false);
void emitVarDecl(const AstVar* nodep, bool asRef = false);
void emitModCUse(AstNodeModule* modp, VUseType useType);
// CONSTRUCTORS

View File

@ -28,90 +28,9 @@
// We use a static char array in VL_VALUE_STRING
constexpr int VL_VALUE_STRING_MAX_WIDTH = 8192;
//######################################################################
// Establish mtask variable sort order in mtasks mode
class EmitVarTspSorter final : public V3TSP::TspStateBase {
private:
// MEMBERS
const MTaskIdSet& m_mtaskIds; // Mtask we're ordering
static unsigned s_serialNext; // Unique ID to establish serial order
unsigned m_serial; // Serial ordering
public:
// CONSTRUCTORS
explicit EmitVarTspSorter(const MTaskIdSet& mtaskIds)
: m_mtaskIds(mtaskIds) { // Cannot be {} or GCC 4.8 false warning
m_serial = ++s_serialNext; // Cannot be ()/{} or GCC 4.8 false warning
}
virtual ~EmitVarTspSorter() = default;
// METHODS
virtual bool operator<(const TspStateBase& other) const override {
return operator<(dynamic_cast<const EmitVarTspSorter&>(other));
}
bool operator<(const EmitVarTspSorter& other) const { return m_serial < other.m_serial; }
const MTaskIdSet& mtaskIds() const { return m_mtaskIds; }
virtual int cost(const TspStateBase* otherp) const override {
return cost(dynamic_cast<const EmitVarTspSorter*>(otherp));
}
virtual int cost(const EmitVarTspSorter* otherp) const {
int cost = diffs(m_mtaskIds, otherp->m_mtaskIds);
cost += diffs(otherp->m_mtaskIds, m_mtaskIds);
return cost;
}
// Returns the number of elements in set_a that don't appear in set_b
static int diffs(const MTaskIdSet& set_a, const MTaskIdSet& set_b) {
int diffs = 0;
for (int i : set_a) {
if (set_b.find(i) == set_b.end()) ++diffs;
}
return diffs;
}
};
unsigned EmitVarTspSorter::s_serialNext = 0;
//######################################################################
// EmitCFunc
void EmitCFunc::emitCtorSep(bool* firstp) {
if (*firstp) {
puts(" : ");
*firstp = false;
} else {
puts(", ");
}
if (ofp()->exceededWidth()) puts("\n ");
}
void EmitCFunc::emitVarCtors(bool* firstp) {
if (!m_ctorVarsVec.empty()) {
ofp()->indentInc();
if (*firstp) puts("\n");
for (const AstVar* varp : m_ctorVarsVec) {
const AstBasicDType* const dtypep = VN_CAST(varp->dtypeSkipRefp(), BasicDType);
if (!dtypep) {
puts("// Skipping array: ");
puts(varp->nameProtect());
puts("\n");
} else if (dtypep->keyword().isMTaskState()) {
emitCtorSep(firstp);
puts(varp->nameProtect());
puts("(");
iterate(varp->valuep());
puts(")");
} else {
emitCtorSep(firstp);
puts(varp->nameProtect());
puts("(");
putsQuoted(varp->nameProtect());
puts(")");
}
}
puts("\n");
ofp()->indentDec();
}
}
bool EmitCFunc::emitSimpleOk(AstNodeMath* nodep) {
// Can we put out a simple (A + B) instead of VL_ADD_III(A,B)?
if (nodep->emitSimpleOperator() == "") return false;
@ -514,192 +433,6 @@ void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const stri
displayEmit(nodep, isScan);
}
void EmitCFunc::emitVarList(AstNode* firstp, EisWhich which, const string& prefixIfImp,
string& sectionr) {
// Put out a list of signal declarations
// in order of 0:clocks, 1:vluint8, 2:vluint16, 4:vluint32, 5:vluint64, 6:wide, 7:arrays
// This aids cache packing and locality
//
// Largest->smallest reduces the number of pad variables. Also
// experimented with alternating between large->small and small->large
// on successive Mtask groups, but then when a new mtask gets added may
// cause a huge delta.
//
// TODO: Move this sort to an earlier visitor stage.
VarSortMap varAnonMap;
VarSortMap varNonanonMap;
for (int isstatic = 1; isstatic >= 0; isstatic--) {
if (prefixIfImp != "" && !isstatic) continue;
for (AstNode* nodep = firstp; nodep; nodep = nodep->nextp()) {
if (const AstVar* varp = VN_CAST(nodep, Var)) {
bool doit = true;
switch (which) {
case EVL_CLASS_IO: doit = varp->isIO(); break;
case EVL_CLASS_SIG:
doit = ((varp->isSignal() || varp->isClassMember()) && !varp->isIO());
break;
case EVL_CLASS_TEMP: doit = (varp->isTemp() && !varp->isIO()); break;
case EVL_CLASS_PAR:
doit = (varp->isParam() && !VN_IS(varp->valuep(), Const));
break;
case EVL_CLASS_ALL: doit = true; break;
default: v3fatalSrc("Bad Case");
}
if (varp->isStatic() ? !isstatic : isstatic) doit = false;
if (doit) {
const int sigbytes = varp->dtypeSkipRefp()->widthAlignBytes();
int sortbytes = 9;
if (varp->isUsedClock() && varp->widthMin() == 1) {
sortbytes = 0;
} else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) {
sortbytes = 8;
} else if (varp->basicp() && varp->basicp()->isOpaque()) {
sortbytes = 7;
} else if (varp->isScBv() || varp->isScBigUint()) {
sortbytes = 6;
} else if (sigbytes == 8) {
sortbytes = 5;
} else if (sigbytes == 4) {
sortbytes = 4;
} else if (sigbytes == 2) {
sortbytes = 2;
} else if (sigbytes == 1) {
sortbytes = 1;
}
const bool anonOk
= (v3Global.opt.compLimitMembers() != 0 // Enabled
&& !varp->isStatic() && !varp->isIO() // Confusing to user
&& !varp->isSc() // Aggregates can't be anon
&& (varp->basicp()
&& !varp->basicp()->isOpaque()) // Aggregates can't be anon
);
if (anonOk) {
varAnonMap[sortbytes].push_back(varp);
} else {
varNonanonMap[sortbytes].push_back(varp);
}
}
}
}
}
if (!varAnonMap.empty() || !varNonanonMap.empty()) {
if (!sectionr.empty()) {
puts(sectionr);
sectionr = "";
}
VarVec anons;
VarVec nonanons;
emitVarSort(varAnonMap, &anons);
emitVarSort(varNonanonMap, &nonanons);
emitSortedVarList(anons, nonanons, prefixIfImp);
}
}
void EmitCFunc::emitVarSort(const VarSortMap& vmap, VarVec* sortedp) {
UASSERT(sortedp->empty(), "Sorted should be initially empty");
if (!v3Global.opt.mtasks()) {
// Plain old serial mode. Sort by size, from small to large,
// to optimize for both packing and small offsets in code.
for (const auto& itr : vmap) {
for (VarVec::const_iterator jt = itr.second.begin(); jt != itr.second.end(); ++jt) {
sortedp->push_back(*jt);
}
}
return;
}
// MacroTask mode. Sort by MTask-affinity group first, size second.
using MTaskVarSortMap = std::map<const MTaskIdSet, VarSortMap>;
MTaskVarSortMap m2v;
for (VarSortMap::const_iterator it = vmap.begin(); it != vmap.end(); ++it) {
const int size_class = it->first;
const VarVec& vec = it->second;
for (const AstVar* varp : vec) { m2v[varp->mtaskIds()][size_class].push_back(varp); }
}
// Create a TSP sort state for each MTaskIdSet footprint
V3TSP::StateVec states;
for (MTaskVarSortMap::iterator it = m2v.begin(); it != m2v.end(); ++it) {
states.push_back(new EmitVarTspSorter(it->first));
}
// Do the TSP sort
V3TSP::StateVec sorted_states;
V3TSP::tspSort(states, &sorted_states);
for (V3TSP::StateVec::iterator it = sorted_states.begin(); it != sorted_states.end(); ++it) {
const EmitVarTspSorter* statep = dynamic_cast<const EmitVarTspSorter*>(*it);
const VarSortMap& localVmap = m2v[statep->mtaskIds()];
// use rbegin/rend to sort size large->small
for (VarSortMap::const_reverse_iterator jt = localVmap.rbegin(); jt != localVmap.rend();
++jt) {
const VarVec& vec = jt->second;
for (VarVec::const_iterator kt = vec.begin(); kt != vec.end(); ++kt) {
sortedp->push_back(*kt);
}
}
VL_DO_DANGLING(delete statep, statep);
}
}
void EmitCFunc::emitSortedVarList(const VarVec& anons, const VarVec& nonanons,
const string& prefixIfImp) {
string curVarCmt;
// Output anons
{
const int anonMembers = anons.size();
const int lim = v3Global.opt.compLimitMembers();
int anonL3s = 1;
int anonL2s = 1;
int anonL1s = 1;
if (anonMembers > (lim * lim * lim)) {
anonL3s = (anonMembers + (lim * lim * lim) - 1) / (lim * lim * lim);
anonL2s = lim;
anonL1s = lim;
} else if (anonMembers > (lim * lim)) {
anonL2s = (anonMembers + (lim * lim) - 1) / (lim * lim);
anonL1s = lim;
} else if (anonMembers > lim) {
anonL1s = (anonMembers + lim - 1) / lim;
}
if (anonL1s != 1)
puts("// Anonymous structures to workaround compiler member-count bugs\n");
auto it = anons.cbegin();
for (int l3 = 0; l3 < anonL3s && it != anons.cend(); ++l3) {
if (anonL3s != 1) puts("struct {\n");
for (int l2 = 0; l2 < anonL2s && it != anons.cend(); ++l2) {
if (anonL2s != 1) puts("struct {\n");
for (int l1 = 0; l1 < anonL1s && it != anons.cend(); ++l1) {
if (anonL1s != 1) puts("struct {\n");
for (int l0 = 0; l0 < lim && it != anons.cend(); ++l0) {
const AstVar* varp = *it;
emitVarDecl(varp, prefixIfImp);
++it;
}
if (anonL1s != 1) puts("};\n");
}
if (anonL2s != 1) puts("};\n");
}
if (anonL3s != 1) puts("};\n");
}
// Leftovers, just in case off by one error somewhere above
for (; it != anons.end(); ++it) {
const AstVar* varp = *it;
emitVarDecl(varp, prefixIfImp);
}
}
// Output nonanons
for (const AstVar* varp : nonanons) {
if (varp->isIO() && varp->isSc()) { m_ctorVarsVec.push_back(varp); }
AstBasicDType* const basicp = varp->basicp();
if (basicp && basicp->keyword().isMTaskState()) { m_ctorVarsVec.push_back(varp); }
emitVarDecl(varp, prefixIfImp);
}
}
void EmitCFunc::emitCCallArgs(AstNodeCCall* nodep) {
bool comma = false;
if (nodep->funcp()->isLoose() && !nodep->funcp()->isStatic()) {

View File

@ -114,12 +114,8 @@ public:
class EmitCFunc VL_NOT_FINAL : public EmitCBaseVisitor {
private:
using VarVec = std::vector<const AstVar*>;
using VarSortMap = std::map<int, VarVec>; // Map size class to VarVec
bool m_suppressSemi;
AstVarRef* m_wideTempRefp; // Variable that _WW macros should be setting
VarVec m_ctorVarsVec; // All variables in constructor order
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
@ -155,18 +151,6 @@ public:
void displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt, bool ignore,
char fmtLetter);
enum EisWhich : uint8_t {
EVL_CLASS_IO,
EVL_CLASS_SIG,
EVL_CLASS_TEMP,
EVL_CLASS_PAR,
EVL_CLASS_ALL
};
void emitVarList(AstNode* firstp, EisWhich which, const string& prefixIfImp, string& sectionr);
static void emitVarSort(const VarSortMap& vmap, VarVec* sortedp);
void emitSortedVarList(const VarVec& anons, const VarVec& nonanons, const string& prefixIfImp);
void emitVarCtors(bool* firstp);
void emitCtorSep(bool* firstp);
bool emitSimpleOk(AstNodeMath* nodep);
void emitIQW(AstNode* nodep) {
// Other abbrevs: "C"har, "S"hort, "F"loat, "D"ouble, stri"N"g
@ -242,7 +226,7 @@ public:
for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) {
if (AstVar* varp = VN_CAST(subnodep, Var)) {
if (varp->isFuncReturn()) emitVarDecl(varp, "");
if (varp->isFuncReturn()) emitVarDecl(varp);
}
}
@ -271,7 +255,7 @@ public:
virtual void visit(AstVar* nodep) override {
UASSERT_OBJ(m_cfuncp, nodep, "Cannot emit non-local variable");
emitVarDecl(nodep, "");
emitVarDecl(nodep);
}
virtual void visit(AstNodeAssign* nodep) override {

View File

@ -90,7 +90,7 @@ class EmitCModel final : public EmitCFunc {
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
if (varp->isPrimaryIO()) { //
emitVarDecl(varp, "", /* asRef: */ true);
emitVarDecl(varp, /* asRef: */ true);
}
}
}

207
src/V3VariableOrder.cpp Normal file
View File

@ -0,0 +1,207 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Variable ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
// V3VariableOrder's Transformations:
//
// Each module:
// Order module variables
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3VariableOrder.h"
#include "V3Ast.h"
#include "V3AstUserAllocator.h"
#include "V3EmitCBase.h"
#include "V3TSP.h"
#include <algorithm>
#include <vector>
//######################################################################
// Establish mtask variable sort order in mtasks mode
class VarTspSorter final : public V3TSP::TspStateBase {
private:
// MEMBERS
const MTaskIdSet& m_mtaskIds; // Mtask we're ordering
static unsigned s_serialNext; // Unique ID to establish serial order
unsigned m_serial; // Serial ordering
public:
// CONSTRUCTORS
explicit VarTspSorter(const MTaskIdSet& mtaskIds)
: m_mtaskIds(mtaskIds) { // Cannot be {} or GCC 4.8 false warning
m_serial = ++s_serialNext; // Cannot be ()/{} or GCC 4.8 false warning
}
virtual ~VarTspSorter() = default;
// METHODS
virtual bool operator<(const TspStateBase& other) const override {
return operator<(dynamic_cast<const VarTspSorter&>(other));
}
bool operator<(const VarTspSorter& other) const { return m_serial < other.m_serial; }
const MTaskIdSet& mtaskIds() const { return m_mtaskIds; }
virtual int cost(const TspStateBase* otherp) const override {
return cost(dynamic_cast<const VarTspSorter*>(otherp));
}
virtual int cost(const VarTspSorter* otherp) const {
int cost = diffs(m_mtaskIds, otherp->m_mtaskIds);
cost += diffs(otherp->m_mtaskIds, m_mtaskIds);
return cost;
}
// Returns the number of elements in set_a that don't appear in set_b
static int diffs(const MTaskIdSet& set_a, const MTaskIdSet& set_b) {
int diffs = 0;
for (int i : set_a) {
if (set_b.find(i) == set_b.end()) ++diffs;
}
return diffs;
}
};
unsigned VarTspSorter::s_serialNext = 0;
class VariableOrder final {
// NODE STATE
// AstVar::user1() -> attributes, via m_attributes
AstUser1InUse m_user1InUse; // AstVar
struct VarAttributes {
uint32_t stratum; // Roughly equivalent to alignment requirement, to avoid padding
bool anonOk; // Can be emitted as part of anonymous structure
};
AstUser1Allocator<AstVar, VarAttributes> m_attributes; // Attributes used for sorting
//######################################################################
// Simple sort
void simpleSortVars(std::vector<AstVar*>& varps) {
stable_sort(varps.begin(), varps.end(),
[this](const AstVar* ap, const AstVar* bp) -> bool {
if (ap->isStatic() != bp->isStatic()) { // Non-statics before statics
return bp->isStatic();
}
const auto& attrA = m_attributes(ap);
const auto& attrB = m_attributes(bp);
if (attrA.anonOk != attrB.anonOk) { // Anons before non-anons
return attrA.anonOk;
}
return attrA.stratum < attrB.stratum; // Finally sort by stratum
});
}
// Sort by MTask-affinity first, then the same as simpleSortVars
void tspSortVars(std::vector<AstVar*>& varps) {
// Map from "MTask affinity" -> "variable list"
std::map<const MTaskIdSet, std::vector<AstVar*>> m2v;
for (AstVar* const varp : varps) { m2v[varp->mtaskIds()].push_back(varp); }
// Create a TSP sort state for each unique MTaskIdSet, except for the empty set
V3TSP::StateVec states;
for (const auto& pair : m2v) {
if (pair.first.empty()) continue;
states.push_back(new VarTspSorter(pair.first));
}
// Do the TSP sort
V3TSP::StateVec sortedStates;
V3TSP::tspSort(states, &sortedStates);
varps.clear();
// Helper function to sort given vector, then append to 'varps'
const auto sortAndAppend = [this, &varps](std::vector<AstVar*>& subVarps) {
simpleSortVars(subVarps);
for (AstVar* const varp : subVarps) { varps.push_back(varp); }
};
// Enumerate by sorted MTaskIdSet, sort within the set separately
for (const V3TSP::TspStateBase* const stateBasep : sortedStates) {
const VarTspSorter* const statep = dynamic_cast<const VarTspSorter*>(stateBasep);
sortAndAppend(m2v[statep->mtaskIds()]);
VL_DO_DANGLING(delete statep, statep);
}
// Finally add the variables with no known MTask affinity
sortAndAppend(m2v[MTaskIdSet()]);
}
void orderModuleVars(AstNodeModule* modp) {
std::vector<AstVar*> varps;
// Unlink all module variables from the module, compute attributes
for (AstNode *nodep = modp->stmtsp(), *nextp; nodep; nodep = nextp) {
nextp = nodep->nextp();
if (AstVar* const varp = VN_CAST(nodep, Var)) {
// Unlink, add to vector
varp->unlinkFrBack();
varps.push_back(varp);
// Compute attributes up front
auto& attributes = m_attributes(varp);
// Stratum
const int sigbytes = varp->dtypeSkipRefp()->widthAlignBytes();
attributes.stratum = (varp->isUsedClock() && varp->widthMin() == 1) ? 0
: VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType) ? 8
: (varp->basicp() && varp->basicp()->isOpaque()) ? 7
: (varp->isScBv() || varp->isScBigUint()) ? 6
: (sigbytes == 8) ? 5
: (sigbytes == 4) ? 4
: (sigbytes == 2) ? 2
: (sigbytes == 1) ? 1
: 9;
// Anonymous structure ok
attributes.anonOk = EmitCBaseVisitor::isAnonOk(varp);
}
}
if (!varps.empty()) {
// Sort variables
if (!v3Global.opt.mtasks()) {
simpleSortVars(varps);
} else {
tspSortVars(varps);
}
// Insert them back under the module, in the new order, but at
// the front of the list so they come out first in dumps/XML.
auto it = varps.cbegin();
AstVar* const firstp = *it++;
for (; it != varps.cend(); ++it) firstp->addNext(*it);
if (AstNode* const stmtsp = modp->stmtsp()) {
stmtsp->unlinkFrBackWithNext();
firstp->addNext(stmtsp);
}
modp->addStmtp(firstp);
}
}
public:
static void processModule(AstNodeModule* modp) { VariableOrder().orderModuleVars(modp); }
};
//######################################################################
// V3VariableOrder static functions
void V3VariableOrder::orderAll() {
UINFO(2, __FUNCTION__ << ": " << endl);
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
modp = VN_CAST(modp->nextp(), NodeModule)) {
VariableOrder::processModule(modp);
}
V3Global::dumpCheckGlobalTree("variableorder", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}

30
src/V3VariableOrder.h Normal file
View File

@ -0,0 +1,30 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Variable ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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_V3VARIABLEORDER_H_
#define VERILATOR_V3VARIABLEORDER_H_
#include "config_build.h"
#include "verilatedos.h"
//============================================================================
class V3VariableOrder final {
public:
static void orderAll();
};
#endif // Guard

View File

@ -96,6 +96,7 @@
#include "V3Undriven.h"
#include "V3Unknown.h"
#include "V3Unroll.h"
#include "V3VariableOrder.h"
#include "V3Waiver.h"
#include "V3Width.h"
@ -507,6 +508,9 @@ static void process() {
// Must be before V3EmitC
V3CUse::cUseAll();
// Order variables
V3VariableOrder::orderAll();
// emitcInlines is first, as it may set needHInlines which other emitters read
V3EmitC::emitcInlines();
V3EmitC::emitcSyms();

View File

@ -18,10 +18,10 @@ compile(
);
if ($Self->{vlt_all}) {
file_grep("$out_filename", qr/\<var fl="d56" loc=".*?" name="formatted" dtype_id="4" dir="input" vartype="string" origName="formatted" sformat="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d77" loc=".*?" name="t.sub.in" dtype_id="3" vartype="int" origName="in" public="true" public_flat_rd="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d78" loc=".*?" name="t.sub.fr_a" dtype_id="3" vartype="int" origName="fr_a" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d79" loc=".*?" name="t.sub.fr_b" dtype_id="3" vartype="int" origName="fr_b" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d56" loc=".*?" name="formatted" dtype_id="\d+" dir="input" vartype="string" origName="formatted" sformat="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d77" loc=".*?" name="t.sub.in" dtype_id="\d+" vartype="int" origName="in" public="true" public_flat_rd="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d78" loc=".*?" name="t.sub.fr_a" dtype_id="\d+" vartype="int" origName="fr_a" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d79" loc=".*?" name="t.sub.fr_b" dtype_id="\d+" vartype="int" origName="fr_b" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
}
execute(

View File

@ -20,10 +20,10 @@ compile(
);
if ($Self->{vlt_all}) {
file_grep("$out_filename", qr/\<var fl="e58" loc=".*?" name="formatted" dtype_id="4" dir="input" vartype="string" origName="formatted" sformat="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e81" loc=".*?" name="t.sub.in" dtype_id="3" vartype="int" origName="in" public="true" public_flat_rd="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e82" loc=".*?" name="t.sub.fr_a" dtype_id="3" vartype="int" origName="fr_a" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e83" loc=".*?" name="t.sub.fr_b" dtype_id="3" vartype="int" origName="fr_b" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e58" loc=".*?" name="formatted" dtype_id="\d+" dir="input" vartype="string" origName="formatted" sformat="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e81" loc=".*?" name="t.sub.in" dtype_id="\d+" vartype="int" origName="in" public="true" public_flat_rd="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e82" loc=".*?" name="t.sub.fr_a" dtype_id="\d+" vartype="int" origName="fr_a" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e83" loc=".*?" name="t.sub.fr_b" dtype_id="\d+" vartype="int" origName="fr_b" public="true" public_flat_rd="true" public_flat_rw="true"\/\>/i);
}
execute(

View File

@ -18,9 +18,9 @@ compile(
);
if ($Self->{vlt_all}) {
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u0.u0.z1" dtype_id="3" vartype="logic" origName="z1"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u0.u1.z1" dtype_id="3" vartype="logic" origName="z1"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u1.u0.z0" dtype_id="3" vartype="logic" origName="z0"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u0.u0.z1" dtype_id="\d+" vartype="logic" origName="z1"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u0.u1.z1" dtype_id="\d+" vartype="logic" origName="z1"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e70" loc=".*?" name="t.u.u1.u0.z0" dtype_id="\d+" vartype="logic" origName="z0"\/\>/i);
}
execute(

View File

@ -19,11 +19,11 @@ compile(
if ($Self->{vlt_all}) {
file_grep($Self->{stats}, qr/Optimizations, isolate_assignments blocks\s+5/i);
file_grep("$out_filename", qr/\<var fl="d23" loc=".*?" name="t.b" dtype_id="4" vartype="logic" origName="b" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d99" loc=".*?" name="__Vfunc_t.file.get_31_16__0__Vfuncout" dtype_id="5" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d100" loc=".*?" name="__Vfunc_t.file.get_31_16__0__t_crc" dtype_id="4" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d112" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_crc" dtype_id="4" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d113" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_c" dtype_id="4" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_c" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d23" loc=".*?" name="t.b" dtype_id="\d+" vartype="logic" origName="b" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d99" loc=".*?" name="__Vfunc_t.file.get_31_16__0__Vfuncout" dtype_id="\d+" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d100" loc=".*?" name="__Vfunc_t.file.get_31_16__0__t_crc" dtype_id="\d+" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d112" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_crc" dtype_id="\d+" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="d113" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_c" dtype_id="\d+" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_c" isolate_assignments="true"\/\>/i);
}
execute(

View File

@ -19,11 +19,11 @@ compile(
if ($Self->{vlt_all}) {
file_grep($Self->{stats}, qr/Optimizations, isolate_assignments blocks\s+5/i);
file_grep("$out_filename", qr/\<var fl="e23" loc=".*?" name="t.b" dtype_id="4" vartype="logic" origName="b" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e104" loc=".*?" name="__Vfunc_t.file.get_31_16__0__Vfuncout" dtype_id="5" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e105" loc=".*?" name="__Vfunc_t.file.get_31_16__0__t_crc" dtype_id="4" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e115" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_crc" dtype_id="4" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e116" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_c" dtype_id="4" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_c" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e23" loc=".*?" name="t.b" dtype_id="\d+" vartype="logic" origName="b" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e104" loc=".*?" name="__Vfunc_t.file.get_31_16__0__Vfuncout" dtype_id="\d+" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e105" loc=".*?" name="__Vfunc_t.file.get_31_16__0__t_crc" dtype_id="\d+" vartype="logic" origName="__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e115" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_crc" dtype_id="\d+" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc" isolate_assignments="true"\/\>/i);
file_grep("$out_filename", qr/\<var fl="e116" loc=".*?" name="__Vtask_t.file.set_b_d__1__t_c" dtype_id="\d+" vartype="logic" origName="__Vtask_t__DOT__file__DOT__set_b_d__1__t_c" isolate_assignments="true"\/\>/i);
}
execute(