Automatically split some packed variables (#5843)
This patch adds a heuristic to V3SplitVar, and it attempts to split up packed variables that are only referenced via constant index, non-overlapping bit/range selects. This can eliminate some UNOPTFLAT cases.
This commit is contained in:
parent
128231b077
commit
d9701e6406
|
|
@ -644,6 +644,11 @@ Summary:
|
||||||
are typically used only when recommended by a maintainer to help debug
|
are typically used only when recommended by a maintainer to help debug
|
||||||
or work around an issue.
|
or work around an issue.
|
||||||
|
|
||||||
|
.. option:: -fno-var-split
|
||||||
|
|
||||||
|
Do not attempt to split variables automatically. Variables explicitly
|
||||||
|
annotated with :option:`/*verilator&32;split_var*/` are still split.
|
||||||
|
|
||||||
.. option:: -future0 <option>
|
.. option:: -future0 <option>
|
||||||
|
|
||||||
Rarely needed. Suppress an unknown Verilator option for an option that
|
Rarely needed. Suppress an unknown Verilator option for an option that
|
||||||
|
|
|
||||||
|
|
@ -550,6 +550,20 @@ or "`ifdef`"'s may break other tools.
|
||||||
large arrays may slow down the Verilation speed, so use this only on
|
large arrays may slow down the Verilation speed, so use this only on
|
||||||
variables that require it.
|
variables that require it.
|
||||||
|
|
||||||
|
Packed variables that are only referenced locally (without hiererchical
|
||||||
|
references) via non-overlaping, constant-indexed bit or part select
|
||||||
|
expressions are split automatically. This covers the somewhat comon usage
|
||||||
|
pattern:
|
||||||
|
|
||||||
|
.. code-block:: sv
|
||||||
|
|
||||||
|
logic [1:0][31:0] tmp;
|
||||||
|
|
||||||
|
assign tmp[0] = foo + a;
|
||||||
|
assign tmp[1] = tmp[1] + b;
|
||||||
|
assign bar = tmp[1] + c;
|
||||||
|
|
||||||
|
|
||||||
Same as :option:`split_var` configuration file option.
|
Same as :option:`split_var` configuration file option.
|
||||||
|
|
||||||
.. option:: /*verilator&32;tag <text...>*/
|
.. option:: /*verilator&32;tag <text...>*/
|
||||||
|
|
|
||||||
|
|
@ -1356,6 +1356,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
|
||||||
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
|
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
|
||||||
DECL_OPTION("-ftable", FOnOff, &m_fTable);
|
DECL_OPTION("-ftable", FOnOff, &m_fTable);
|
||||||
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
|
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
|
||||||
|
DECL_OPTION("-fvar-split", FOnOff, &m_fVarSplit);
|
||||||
|
|
||||||
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
|
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
|
||||||
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
|
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
|
||||||
|
|
@ -2168,6 +2169,7 @@ void V3Options::optimize(int level) {
|
||||||
m_fSubst = flag;
|
m_fSubst = flag;
|
||||||
m_fSubstConst = flag;
|
m_fSubstConst = flag;
|
||||||
m_fTable = flag;
|
m_fTable = flag;
|
||||||
|
m_fVarSplit = flag;
|
||||||
// And set specific optimization levels
|
// And set specific optimization levels
|
||||||
if (level >= 3) {
|
if (level >= 3) {
|
||||||
m_inlineMult = -1; // Maximum inlining
|
m_inlineMult = -1; // Maximum inlining
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,7 @@ private:
|
||||||
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
||||||
bool m_fTable; // main switch: -fno-table: lookup table creation
|
bool m_fTable; // main switch: -fno-table: lookup table creation
|
||||||
bool m_fTaskifyAll = false; // main switch: --ftaskify-all-forked
|
bool m_fTaskifyAll = false; // main switch: --ftaskify-all-forked
|
||||||
|
bool m_fVarSplit; // main switch: -fno-var-split: automatic variable splitting
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
bool m_available = false; // Set to true at the end of option parsing
|
bool m_available = false; // Set to true at the end of option parsing
|
||||||
|
|
@ -717,6 +718,7 @@ public:
|
||||||
bool fSubstConst() const { return m_fSubstConst; }
|
bool fSubstConst() const { return m_fSubstConst; }
|
||||||
bool fTable() const { return m_fTable; }
|
bool fTable() const { return m_fTable; }
|
||||||
bool fTaskifyAll() const { return m_fTaskifyAll; }
|
bool fTaskifyAll() const { return m_fTaskifyAll; }
|
||||||
|
bool fVarSplit() const { return m_fVarSplit; }
|
||||||
|
|
||||||
string traceClassBase() const VL_MT_SAFE { return m_traceFormat.classBase(); }
|
string traceClassBase() const VL_MT_SAFE { return m_traceFormat.classBase(); }
|
||||||
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@
|
||||||
|
|
||||||
#include "V3SplitVar.h"
|
#include "V3SplitVar.h"
|
||||||
|
|
||||||
|
#include "V3AstUserAllocator.h"
|
||||||
#include "V3Stats.h"
|
#include "V3Stats.h"
|
||||||
#include "V3UniqueNames.h"
|
#include "V3UniqueNames.h"
|
||||||
|
|
||||||
|
|
@ -181,6 +182,7 @@ struct SplitVarImpl VL_NOT_FINAL {
|
||||||
}
|
}
|
||||||
if (varp->isSigPublic()) return "it is public";
|
if (varp->isSigPublic()) return "it is public";
|
||||||
if (varp->isUsedLoopIdx()) return "it is used as a loop variable";
|
if (varp->isUsedLoopIdx()) return "it is used as a loop variable";
|
||||||
|
if (varp->isForceable()) return "it is forceable";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -392,7 +394,10 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using SplitVarRefsMap = std::map<AstNodeModule*, RefsInModule, AstNodeComparator>;
|
struct SplitVarRefs final {
|
||||||
|
std::map<AstNodeModule*, RefsInModule, AstNodeComparator> m_refs;
|
||||||
|
std::unordered_set<AstVar*> m_hasXref;
|
||||||
|
};
|
||||||
|
|
||||||
class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
using VarSet = std::set<AstVar*, AstNodeComparator>;
|
using VarSet = std::set<AstVar*, AstNodeComparator>;
|
||||||
|
|
@ -404,7 +409,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
const AstNodeFTask* m_inFTaskp = nullptr;
|
const AstNodeFTask* m_inFTaskp = nullptr;
|
||||||
size_t m_numSplit = 0;
|
size_t m_numSplit = 0;
|
||||||
// List for SplitPackedVarVisitor
|
// List for SplitPackedVarVisitor
|
||||||
SplitVarRefsMap m_refsForPackedSplit;
|
SplitVarRefs m_forPackedSplit;
|
||||||
V3UniqueNames m_tempNames; // For generating unique temporary variable names
|
V3UniqueNames m_tempNames; // For generating unique temporary variable names
|
||||||
|
|
||||||
static AstVarRef* isTargetVref(AstNode* nodep) {
|
static AstVarRef* isTargetVref(AstNode* nodep) {
|
||||||
|
|
@ -436,19 +441,19 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
}
|
}
|
||||||
void pushDeletep(AstNode* nodep) { // overriding VNVisitor::pusDeletep()
|
void pushDeletep(AstNode* nodep) { // overriding VNVisitor::pusDeletep()
|
||||||
UASSERT_OBJ(m_modp, nodep, "Must not nullptr");
|
UASSERT_OBJ(m_modp, nodep, "Must not nullptr");
|
||||||
m_refsForPackedSplit[m_modp].remove(nodep);
|
m_forPackedSplit.m_refs[m_modp].remove(nodep);
|
||||||
VNVisitor::pushDeletep(nodep);
|
VNVisitor::pushDeletep(nodep);
|
||||||
}
|
}
|
||||||
AstVar* newVar(FileLine* fl, VVarType type, const std::string& name, AstNodeDType* dtp) {
|
AstVar* newVar(FileLine* fl, VVarType type, const std::string& name, AstNodeDType* dtp) {
|
||||||
AstVar* const varp = new AstVar{fl, type, name, dtp};
|
AstVar* const varp = new AstVar{fl, type, name, dtp};
|
||||||
UASSERT_OBJ(m_modp, varp, "Must not nullptr");
|
UASSERT_OBJ(m_modp, varp, "Must not nullptr");
|
||||||
m_refsForPackedSplit[m_modp].add(varp);
|
m_forPackedSplit.m_refs[m_modp].add(varp);
|
||||||
return varp;
|
return varp;
|
||||||
}
|
}
|
||||||
AstVarRef* newVarRef(FileLine* fl, AstVar* varp, const VAccess& access) {
|
AstVarRef* newVarRef(FileLine* fl, AstVar* varp, const VAccess& access) {
|
||||||
AstVarRef* const refp = new AstVarRef{fl, varp, access};
|
AstVarRef* const refp = new AstVarRef{fl, varp, access};
|
||||||
UASSERT_OBJ(m_modp, refp, "Must not nullptr");
|
UASSERT_OBJ(m_modp, refp, "Must not nullptr");
|
||||||
m_refsForPackedSplit[m_modp].add(refp);
|
m_forPackedSplit.m_refs[m_modp].add(refp);
|
||||||
return refp;
|
return refp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,22 +544,26 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void visit(AstVar* nodep) override {
|
void visit(AstVar* nodep) override {
|
||||||
|
m_forPackedSplit.m_refs[m_modp].add(nodep);
|
||||||
if (!nodep->attrSplitVar()) return; // Nothing to do
|
if (!nodep->attrSplitVar()) return; // Nothing to do
|
||||||
if (!cannotSplitReason(nodep)) {
|
if (!cannotSplitReason(nodep)) {
|
||||||
m_refs.registerVar(nodep);
|
m_refs.registerVar(nodep);
|
||||||
UINFO(4, nodep->name() << " is added to candidate list.\n");
|
UINFO(4, nodep->name() << " is added to candidate list.\n");
|
||||||
}
|
}
|
||||||
m_refsForPackedSplit[m_modp].add(nodep);
|
|
||||||
}
|
}
|
||||||
void visit(AstVarRef* nodep) override {
|
void visit(AstVarRef* nodep) override {
|
||||||
|
m_forPackedSplit.m_refs[m_modp].add(nodep);
|
||||||
if (!nodep->varp()->attrSplitVar()) return; // Nothing to do
|
if (!nodep->varp()->attrSplitVar()) return; // Nothing to do
|
||||||
if (m_refs.tryAdd(m_contextp, nodep, m_inFTaskp)) {
|
if (m_refs.tryAdd(m_contextp, nodep, m_inFTaskp)) {
|
||||||
m_foundTargetVar.insert(nodep->varp());
|
m_foundTargetVar.insert(nodep->varp());
|
||||||
}
|
}
|
||||||
m_refsForPackedSplit[m_modp].add(nodep);
|
}
|
||||||
|
void visit(AstVarXRef* nodep) override {
|
||||||
|
UINFO(4, nodep->varp() << " Has hierarchical reference\n");
|
||||||
|
m_forPackedSplit.m_hasXref.emplace(nodep->varp());
|
||||||
}
|
}
|
||||||
void visit(AstSel* nodep) override {
|
void visit(AstSel* nodep) override {
|
||||||
if (VN_IS(nodep->fromp(), VarRef)) m_refsForPackedSplit[m_modp].add(nodep);
|
if (VN_IS(nodep->fromp(), VarRef)) m_forPackedSplit.m_refs[m_modp].add(nodep);
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
void visit(AstArraySel* nodep) override {
|
void visit(AstArraySel* nodep) override {
|
||||||
|
|
@ -737,7 +746,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
connectPort(varp, vars, nullptr);
|
connectPort(varp, vars, nullptr);
|
||||||
}
|
}
|
||||||
varp->attrSplitVar(!cannotSplitPackedVarReason(varp));
|
varp->attrSplitVar(!cannotSplitPackedVarReason(varp));
|
||||||
m_refsForPackedSplit[m_modp].add(varp);
|
m_forPackedSplit.m_refs[m_modp].add(varp);
|
||||||
} else {
|
} else {
|
||||||
pushDeletep(varp->unlinkFrBack());
|
pushDeletep(varp->unlinkFrBack());
|
||||||
}
|
}
|
||||||
|
|
@ -764,9 +773,9 @@ public:
|
||||||
}
|
}
|
||||||
~SplitUnpackedVarVisitor() override {
|
~SplitUnpackedVarVisitor() override {
|
||||||
UASSERT(m_refs.empty(), "Don't forget to call split()");
|
UASSERT(m_refs.empty(), "Don't forget to call split()");
|
||||||
V3Stats::addStat("SplitVar, Split unpacked arrays", m_numSplit);
|
V3Stats::addStat("SplitVar, unpacked arrays split due to attribute", m_numSplit);
|
||||||
}
|
}
|
||||||
const SplitVarRefsMap& getPackedVarRefs() const { return m_refsForPackedSplit; }
|
const SplitVarRefs& getPackedVarRefs() const { return std::move(m_forPackedSplit); }
|
||||||
|
|
||||||
// Check if the passed variable can be split.
|
// Check if the passed variable can be split.
|
||||||
// Even if this function returns true, the variable may not be split
|
// Even if this function returns true, the variable may not be split
|
||||||
|
|
@ -954,19 +963,27 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
|
// NODE STATE
|
||||||
|
// AstVar::user2() -> bool. Automatically considered candidate
|
||||||
|
// AstVar::user3() -> VarInfo. Used only in findCandidates
|
||||||
|
const VNUser2InUse m_user2InUse;
|
||||||
|
|
||||||
AstNetlist* const m_netp;
|
AstNetlist* const m_netp;
|
||||||
const AstNodeModule* m_modp = nullptr; // Current module (just for log)
|
const AstNodeModule* m_modp = nullptr; // Current module (just for log)
|
||||||
int m_numSplit = 0; // Total number of split variables
|
int m_numSplitAttr = 0; // Number of variables split due to attribute
|
||||||
|
int m_numSplitAuto = 0; // Number of variables split automatically
|
||||||
// key:variable to be split. value:location where the variable is referenced.
|
// key:variable to be split. value:location where the variable is referenced.
|
||||||
std::map<AstVar*, PackedVarRef, AstNodeComparator> m_refs;
|
std::map<AstVar*, PackedVarRef, AstNodeComparator> m_refs;
|
||||||
void visit(AstNodeFTask* nodep) override {
|
void visit(AstNodeFTask* nodep) override {
|
||||||
if (!cannotSplitTaskReason(nodep)) iterateChildren(nodep);
|
if (!cannotSplitTaskReason(nodep)) iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
void visit(AstVar* nodep) override {
|
void visit(AstVar* nodep) override {
|
||||||
if (!nodep->attrSplitVar()) return; // Nothing to do
|
if (!nodep->attrSplitVar() && !nodep->user2()) return; // Nothing to do
|
||||||
if (const char* const reason = cannotSplitReason(nodep, true)) {
|
if (const char* const reason = cannotSplitReason(nodep, true)) {
|
||||||
warnNoSplit(nodep, nodep, reason);
|
if (nodep->attrSplitVar()) {
|
||||||
nodep->attrSplitVar(false);
|
warnNoSplit(nodep, nodep, reason);
|
||||||
|
nodep->attrSplitVar(false);
|
||||||
|
}
|
||||||
} else { // Finally find a good candidate
|
} else { // Finally find a good candidate
|
||||||
const bool inserted = m_refs.emplace(nodep, PackedVarRef{nodep}).second;
|
const bool inserted = m_refs.emplace(nodep, PackedVarRef{nodep}).second;
|
||||||
if (inserted) UINFO(4, nodep->prettyNameQ() << " is added to candidate list.\n");
|
if (inserted) UINFO(4, nodep->prettyNameQ() << " is added to candidate list.\n");
|
||||||
|
|
@ -977,7 +994,7 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
visit(varp);
|
visit(varp);
|
||||||
const auto refit = m_refs.find(varp);
|
const auto refit = m_refs.find(varp);
|
||||||
if (refit == m_refs.end()) return; // variable without split_var metacomment
|
if (refit == m_refs.end()) return; // variable without split_var metacomment
|
||||||
UASSERT_OBJ(varp->attrSplitVar(), varp, "split_var attribute must be attached");
|
UASSERT_OBJ(varp->attrSplitVar() || varp->user2(), varp, "must be a split candidate");
|
||||||
UASSERT_OBJ(!nodep->classOrPackagep(), nodep,
|
UASSERT_OBJ(!nodep->classOrPackagep(), nodep,
|
||||||
"variable in package must have been dropped beforehand.");
|
"variable in package must have been dropped beforehand.");
|
||||||
const AstBasicDType* const basicp = refit->second.basicp();
|
const AstBasicDType* const basicp = refit->second.basicp();
|
||||||
|
|
@ -999,7 +1016,7 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
return; // Variable without split_var metacomment
|
return; // Variable without split_var metacomment
|
||||||
}
|
}
|
||||||
UASSERT_OBJ(varp->attrSplitVar(), varp, "split_var attribute must be attached");
|
UASSERT_OBJ(varp->attrSplitVar() || varp->user2(), varp, "must be a split candidate");
|
||||||
|
|
||||||
const std::array<AstConst*, 2> consts
|
const std::array<AstConst*, 2> consts
|
||||||
= {{VN_CAST(nodep->lsbp(), Const),
|
= {{VN_CAST(nodep->lsbp(), Const),
|
||||||
|
|
@ -1013,7 +1030,10 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
<< " [" << consts[0]->toSInt() << ":+" << consts[1]->toSInt()
|
<< " [" << consts[0]->toSInt() << ":+" << consts[1]->toSInt()
|
||||||
<< "] lsb:" << refit->second.basicp()->lo() << "\n");
|
<< "] lsb:" << refit->second.basicp()->lo() << "\n");
|
||||||
} else {
|
} else {
|
||||||
warnNoSplit(vrefp->varp(), nodep, "its bit range cannot be determined statically");
|
if (varp->attrSplitVar()) {
|
||||||
|
warnNoSplit(vrefp->varp(), nodep, "its bit range cannot be determined statically");
|
||||||
|
varp->attrSplitVar(false);
|
||||||
|
}
|
||||||
if (!consts[0]) {
|
if (!consts[0]) {
|
||||||
UINFO(4, "LSB " << nodep->lsbp() << " is expected to be constant, but not\n");
|
UINFO(4, "LSB " << nodep->lsbp() << " is expected to be constant, but not\n");
|
||||||
}
|
}
|
||||||
|
|
@ -1021,7 +1041,6 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
UINFO(4, "WIDTH " << nodep->widthp() << " is expected to be constant, but not\n");
|
UINFO(4, "WIDTH " << nodep->widthp() << " is expected to be constant, but not\n");
|
||||||
}
|
}
|
||||||
m_refs.erase(varp);
|
m_refs.erase(varp);
|
||||||
varp->attrSplitVar(false);
|
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1169,6 +1188,12 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
if (vars.size() == 1 && vars.front().bitwidth() == varp->width())
|
if (vars.size() == 1 && vars.front().bitwidth() == varp->width())
|
||||||
continue; // No split
|
continue; // No split
|
||||||
|
|
||||||
|
if (varp->attrSplitVar()) {
|
||||||
|
++m_numSplitAttr;
|
||||||
|
} else {
|
||||||
|
++m_numSplitAuto;
|
||||||
|
}
|
||||||
|
|
||||||
createVars(varp, ref.basicp(), vars); // Add the split variables
|
createVars(varp, ref.basicp(), vars); // Add the split variables
|
||||||
|
|
||||||
updateReferences(varp, ref, vars);
|
updateReferences(varp, ref, vars);
|
||||||
|
|
@ -1191,18 +1216,114 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
||||||
} else { // the original variable is not used anymore.
|
} else { // the original variable is not used anymore.
|
||||||
VL_DO_DANGLING(varp->unlinkFrBack()->deleteTree(), varp);
|
VL_DO_DANGLING(varp->unlinkFrBack()->deleteTree(), varp);
|
||||||
}
|
}
|
||||||
++m_numSplit;
|
|
||||||
}
|
}
|
||||||
m_refs.clear(); // Done
|
m_refs.clear(); // Done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find Vars only referenced through non-overlapping constant selects,
|
||||||
|
// and set their user2 to mark them as split candidates
|
||||||
|
static void findCandidates(const RefsInModule& refSets,
|
||||||
|
const std::unordered_set<AstVar*>& hasXrefs) {
|
||||||
|
// Inclusive index range
|
||||||
|
using Range = std::pair<int32_t, int32_t>;
|
||||||
|
|
||||||
|
// Store one VarInfo per AstVar via user3
|
||||||
|
struct VarInfo final {
|
||||||
|
bool ineligible = false; // Ineligible for automatic consideration
|
||||||
|
std::vector<Range> ranges; // [lsb, msb] inclusive of Sels
|
||||||
|
};
|
||||||
|
const VNUser3InUse user3InUse;
|
||||||
|
AstUser3Allocator<AstVar, VarInfo> varInfos;
|
||||||
|
|
||||||
|
// Gather all Sels selecting from each variable, also mark if ineligible
|
||||||
|
for (const AstVarRef* const vrefp : refSets.m_refs) {
|
||||||
|
AstVar* const varp = vrefp->varp();
|
||||||
|
VarInfo& info = varInfos(varp);
|
||||||
|
if (info.ineligible) continue;
|
||||||
|
|
||||||
|
// Function return values seem not safe for splitting, even though
|
||||||
|
// the code above seems like it's tryinig to handle them.
|
||||||
|
if (varp->isFuncReturn()) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Don't consider ports, we don't know what is connected to them at this point
|
||||||
|
if (varp->isIO()) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Can't split variables referenced from outside the module
|
||||||
|
if (hasXrefs.count(varp)) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ineligible if it is not being Sel from
|
||||||
|
AstSel* const selp = VN_CAST(vrefp->firstAbovep(), Sel);
|
||||||
|
if (!selp || vrefp != selp->fromp()) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ineligible if the selection range is not constant
|
||||||
|
AstConst* const lsbConstp = VN_CAST(selp->lsbp(), Const);
|
||||||
|
if (!lsbConstp) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AstConst* const widthConstp = VN_CAST(selp->widthp(), Const);
|
||||||
|
if (!widthConstp) {
|
||||||
|
info.ineligible = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All good, record the selection range
|
||||||
|
const int32_t lsb = lsbConstp->toSInt();
|
||||||
|
const int32_t msb = lsb + widthConstp->toSInt() - 1;
|
||||||
|
info.ranges.emplace_back(lsb, msb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the usage of each variable
|
||||||
|
for (AstVar* const varp : refSets.m_vars) {
|
||||||
|
VarInfo* const infop = varInfos.tryGet(varp);
|
||||||
|
if (!infop) continue;
|
||||||
|
// Don't consider if ineligible
|
||||||
|
if (infop->ineligible) continue;
|
||||||
|
// Sort ranges by LSB then MSB
|
||||||
|
std::sort(infop->ranges.begin(), infop->ranges.end(),
|
||||||
|
[](const Range& a, const Range& b) {
|
||||||
|
if (a.first != b.first) return a.first < b.first;
|
||||||
|
return a.second < b.second;
|
||||||
|
});
|
||||||
|
// Check for overlapping but non-identical ranges
|
||||||
|
bool overlap = false;
|
||||||
|
for (size_t i = 0; i + 1 < infop->ranges.size(); ++i) {
|
||||||
|
const Range& a = infop->ranges[i];
|
||||||
|
const Range& b = infop->ranges[i + 1];
|
||||||
|
// OK if the two ranges are the same
|
||||||
|
if (a == b) continue;
|
||||||
|
// OK if they don't overlap
|
||||||
|
if (a.second < b.first) continue;
|
||||||
|
// Overlap found
|
||||||
|
overlap = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If no overlapping ranges, consider it for automatic splitting
|
||||||
|
varp->user2(!overlap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// When reusing the information from SplitUnpackedVarVisitor
|
// When reusing the information from SplitUnpackedVarVisitor
|
||||||
SplitPackedVarVisitor(AstNetlist* nodep, SplitVarRefsMap& refs)
|
SplitPackedVarVisitor(AstNetlist* nodep, SplitVarRefs fromUnpackedSplit)
|
||||||
: m_netp{nodep} {
|
: m_netp{nodep} {
|
||||||
// If you want ignore refs and walk the tne entire AST,
|
// If you want ignore refs and walk the tne entire AST,
|
||||||
// just call iterateChildren(m_modp) and split() for each module
|
// just call iterateChildren(m_modp) and split() for each module
|
||||||
for (auto& i : refs) {
|
if (v3Global.opt.fVarSplit()) {
|
||||||
|
for (const auto& i : fromUnpackedSplit.m_refs) {
|
||||||
|
findCandidates(i.second, fromUnpackedSplit.m_hasXref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& i : fromUnpackedSplit.m_refs) {
|
||||||
m_modp = i.first;
|
m_modp = i.first;
|
||||||
i.second.visit(this);
|
i.second.visit(this);
|
||||||
split();
|
split();
|
||||||
|
|
@ -1211,7 +1332,8 @@ public:
|
||||||
}
|
}
|
||||||
~SplitPackedVarVisitor() override {
|
~SplitPackedVarVisitor() override {
|
||||||
UASSERT(m_refs.empty(), "Forgot to call split()");
|
UASSERT(m_refs.empty(), "Forgot to call split()");
|
||||||
V3Stats::addStat("SplitVar, Split packed variables", m_numSplit);
|
V3Stats::addStat("SplitVar, packed variables split due to attribute", m_numSplitAttr);
|
||||||
|
V3Stats::addStat("SplitVar, packed variables split automatically", m_numSplitAuto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the passed variable can be split.
|
// Check if the passed variable can be split.
|
||||||
|
|
@ -1247,13 +1369,13 @@ const char* SplitVarImpl::cannotSplitPackedVarReason(const AstVar* varp) {
|
||||||
|
|
||||||
void V3SplitVar::splitVariable(AstNetlist* nodep) {
|
void V3SplitVar::splitVariable(AstNetlist* nodep) {
|
||||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||||
SplitVarRefsMap refs;
|
SplitVarRefs refs;
|
||||||
{
|
{
|
||||||
const SplitUnpackedVarVisitor visitor{nodep};
|
const SplitUnpackedVarVisitor visitor{nodep};
|
||||||
refs = visitor.getPackedVarRefs();
|
refs = visitor.getPackedVarRefs();
|
||||||
}
|
}
|
||||||
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
||||||
{ SplitPackedVarVisitor{nodep, refs}; }
|
{ SplitPackedVarVisitor{nodep, std::move(refs)}; }
|
||||||
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ import vltest_bootstrap
|
||||||
|
|
||||||
test.scenarios('vlt')
|
test.scenarios('vlt')
|
||||||
|
|
||||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
test.lint(verilator_flags2=["-fno-var-split"], fails=True, expect_filename=test.golden_filename)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,10 @@ import vltest_bootstrap
|
||||||
|
|
||||||
test.scenarios('vlt')
|
test.scenarios('vlt')
|
||||||
|
|
||||||
test.lint(verilator_flags2=['--stats', '--expand-limit 5'])
|
test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split'])
|
||||||
|
|
||||||
test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1)
|
test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1)
|
||||||
test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 9)
|
test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 9)
|
||||||
|
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test.
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
if test.vlt:
|
if test.vlt:
|
||||||
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 45)
|
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 44)
|
||||||
|
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.t_dir + "/t_
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
if test.vlt:
|
if test.vlt:
|
||||||
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 40)
|
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 39)
|
||||||
|
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:17:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array'
|
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:18:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array'
|
||||||
17 | logic [1:0][3:0] array;
|
18 | logic [1:0][3:0] array;
|
||||||
| ^~~~~
|
| ^~~~~
|
||||||
t/t_order_blkandnblk_bad.v:19:25: ... Location of blocking assignment
|
t/t_order_blkandnblk_bad.v:20:25: ... Location of blocking assignment
|
||||||
19 | always_comb array[0] = i;
|
20 | always_comb array[0] = i;
|
||||||
| ^
|
| ^
|
||||||
t/t_order_blkandnblk_bad.v:22:15: ... Location of nonblocking assignment
|
t/t_order_blkandnblk_bad.v:23:15: ... Location of nonblocking assignment
|
||||||
22 | array[1] <= array[0];
|
23 | array[1] <= array[0];
|
||||||
| ^~
|
| ^~
|
||||||
... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest
|
... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest
|
||||||
%Error: Exiting due to
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ module t (/*AUTOARG*/
|
||||||
// Outputs
|
// Outputs
|
||||||
o,
|
o,
|
||||||
// Inputs
|
// Inputs
|
||||||
clk, i
|
clk, i, idx
|
||||||
);
|
);
|
||||||
input clk;
|
input clk;
|
||||||
input [3:0] i;
|
input [3:0] i;
|
||||||
|
input idx;
|
||||||
output [3:0] o;
|
output [3:0] o;
|
||||||
|
|
||||||
logic [1:0][3:0] array;
|
logic [1:0][3:0] array;
|
||||||
|
|
@ -21,6 +22,6 @@ module t (/*AUTOARG*/
|
||||||
always @ (posedge clk)
|
always @ (posedge clk)
|
||||||
array[1] <= array[0];
|
array[1] <= array[0];
|
||||||
|
|
||||||
assign o = array[1];
|
assign o = array[idx];
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt"],
|
||||||
|
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 15)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split automatically\s+(\d+)', 0)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split unpacked arrays\s+(\d+)', 27)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split due to attribute\s+(\d+)', 15)
|
||||||
|
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 27)
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ test.compile(verilator_flags2=['--cc --trace --stats +define+TEST_ATTRIBUTES'],
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
test.vcd_identical(test.trace_filename, test.golden_filename)
|
test.vcd_identical(test.trace_filename, test.golden_filename)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 12)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split due to attribute\s+(\d+)', 12)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split unpacked arrays\s+(\d+)', 27)
|
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 27)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--stats'])
|
||||||
|
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 0)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split due to attribute\s+(\d+)', 0)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split unpacked arrays\s+(\d+)', 3)
|
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 3)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1'])
|
||||||
|
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 1)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split due to attribute\s+(\d+)', 1)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split unpacked arrays\s+(\d+)', 0)
|
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 0)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ test.compile(verilator_flags2=['--stats'])
|
||||||
|
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 0)
|
test.file_grep(test.stats, r'SplitVar,\s+packed variables split due to attribute\s+(\d+)', 0)
|
||||||
test.file_grep(test.stats, r'SplitVar,\s+Split unpacked arrays\s+(\d+)', 0)
|
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 0)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 2025 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('simulator')
|
||||||
|
|
||||||
|
test.compile(verilator_flags2=["--stats"])
|
||||||
|
|
||||||
|
test.execute()
|
||||||
|
|
||||||
|
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1)
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||||
|
// any use, without warranty, 2025 by Wilson Snyder.
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
`define stop $stop
|
||||||
|
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||||
|
|
||||||
|
module t(/*AUTOARG*/
|
||||||
|
// Inputs
|
||||||
|
clk
|
||||||
|
);
|
||||||
|
|
||||||
|
input clk;
|
||||||
|
|
||||||
|
logic [31:0] cnt = 0;
|
||||||
|
|
||||||
|
logic [31:0] out0;
|
||||||
|
logic [31:0] out1;
|
||||||
|
logic [31:0] out2;
|
||||||
|
logic [31:0] out3;
|
||||||
|
|
||||||
|
// Splittable
|
||||||
|
sub #(.ADDEND(1), .FORCEABLE(1'b0)) sub_0(clk, cnt, out0);
|
||||||
|
// Unsplittable due to hierarchical reference
|
||||||
|
sub #(.ADDEND(2), .FORCEABLE(1'b0)) sub_1(clk, cnt, out1);
|
||||||
|
// Unsplittable due to hiererchical reference
|
||||||
|
sub #(.ADDEND(3), .FORCEABLE(1'b0)) sub_2(clk, cnt, out2);
|
||||||
|
// Unsplittable due to forceable attribute
|
||||||
|
sub #(.ADDEND(4), .FORCEABLE(1'b1)) sub_3(clk, cnt, out3);
|
||||||
|
|
||||||
|
task print();
|
||||||
|
// This hierarchical reference should prevent automatic splitting
|
||||||
|
$display("sub_2.gen_else.tmp[9]: %02d", sub_2.gen_else.tmp[9]);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
`checkh(out0, cnt + 32'd10);
|
||||||
|
`checkh(out1, cnt + 32'd20);
|
||||||
|
`checkh(out2, cnt + 32'd30);
|
||||||
|
`checkh(out3, cnt + 32'd40);
|
||||||
|
|
||||||
|
// This hierarchical reference should prevent automatic splitting
|
||||||
|
$display("sub_1.gen_else.tmp[9]: %02d", sub_1.gen_else.tmp[9]);
|
||||||
|
print();
|
||||||
|
|
||||||
|
cnt <= cnt + 32'd1;
|
||||||
|
|
||||||
|
if (cnt == 20) begin
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module sub #(
|
||||||
|
parameter logic [31:0] ADDEND,
|
||||||
|
parameter logic FORCEABLE
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input logic [31:0] i,
|
||||||
|
output logic [31:0] o
|
||||||
|
);
|
||||||
|
/* verilator lint_off UNOPTFLAT */
|
||||||
|
// Both branches do the same thing, difference is the 'forceable' attribute
|
||||||
|
if (FORCEABLE) begin : gen_then
|
||||||
|
wire logic [9:0][31:0] tmp /* verilator forceable */;
|
||||||
|
assign tmp[0] = i;
|
||||||
|
for (genvar n = 1; n < 10; ++n) begin
|
||||||
|
assign tmp[n] = tmp[n-1] + ADDEND;
|
||||||
|
end
|
||||||
|
assign o = tmp[9] + ADDEND;
|
||||||
|
end else begin : gen_else
|
||||||
|
wire logic [9:0][31:0] tmp;
|
||||||
|
assign tmp[0] = i;
|
||||||
|
for (genvar n = 1; n < 10; ++n) begin
|
||||||
|
assign tmp[n] = tmp[n-1] + ADDEND;
|
||||||
|
end
|
||||||
|
assign o = tmp[9] + ADDEND;
|
||||||
|
end
|
||||||
|
|
||||||
|
/* verilator lint_on UNOPTFLAT */
|
||||||
|
endmodule
|
||||||
Loading…
Reference in New Issue