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:
Geza Lore 2025-03-09 14:31:01 +00:00 committed by GitHub
parent 128231b077
commit d9701e6406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 302 additions and 47 deletions

View File

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

View File

@ -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...>*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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