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
|
||||
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>
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
.. 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("-ftable", FOnOff, &m_fTable);
|
||||
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("-gate-stmts", Set, &m_gateStmts);
|
||||
|
@ -2168,6 +2169,7 @@ void V3Options::optimize(int level) {
|
|||
m_fSubst = flag;
|
||||
m_fSubstConst = flag;
|
||||
m_fTable = flag;
|
||||
m_fVarSplit = flag;
|
||||
// And set specific optimization levels
|
||||
if (level >= 3) {
|
||||
m_inlineMult = -1; // Maximum inlining
|
||||
|
|
|
@ -411,6 +411,7 @@ private:
|
|||
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
||||
bool m_fTable; // main switch: -fno-table: lookup table creation
|
||||
bool m_fTaskifyAll = false; // main switch: --ftaskify-all-forked
|
||||
bool m_fVarSplit; // main switch: -fno-var-split: automatic variable splitting
|
||||
// clang-format on
|
||||
|
||||
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 fTable() const { return m_fTable; }
|
||||
bool fTaskifyAll() const { return m_fTaskifyAll; }
|
||||
bool fVarSplit() const { return m_fVarSplit; }
|
||||
|
||||
string traceClassBase() const VL_MT_SAFE { return m_traceFormat.classBase(); }
|
||||
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
|
||||
#include "V3SplitVar.h"
|
||||
|
||||
#include "V3AstUserAllocator.h"
|
||||
#include "V3Stats.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
|
@ -181,6 +182,7 @@ struct SplitVarImpl VL_NOT_FINAL {
|
|||
}
|
||||
if (varp->isSigPublic()) return "it is public";
|
||||
if (varp->isUsedLoopIdx()) return "it is used as a loop variable";
|
||||
if (varp->isForceable()) return "it is forceable";
|
||||
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 {
|
||||
using VarSet = std::set<AstVar*, AstNodeComparator>;
|
||||
|
@ -404,7 +409,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
const AstNodeFTask* m_inFTaskp = nullptr;
|
||||
size_t m_numSplit = 0;
|
||||
// List for SplitPackedVarVisitor
|
||||
SplitVarRefsMap m_refsForPackedSplit;
|
||||
SplitVarRefs m_forPackedSplit;
|
||||
V3UniqueNames m_tempNames; // For generating unique temporary variable names
|
||||
|
||||
static AstVarRef* isTargetVref(AstNode* nodep) {
|
||||
|
@ -436,19 +441,19 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
}
|
||||
void pushDeletep(AstNode* nodep) { // overriding VNVisitor::pusDeletep()
|
||||
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);
|
||||
}
|
||||
AstVar* newVar(FileLine* fl, VVarType type, const std::string& name, AstNodeDType* dtp) {
|
||||
AstVar* const varp = new AstVar{fl, type, name, dtp};
|
||||
UASSERT_OBJ(m_modp, varp, "Must not nullptr");
|
||||
m_refsForPackedSplit[m_modp].add(varp);
|
||||
m_forPackedSplit.m_refs[m_modp].add(varp);
|
||||
return varp;
|
||||
}
|
||||
AstVarRef* newVarRef(FileLine* fl, AstVar* varp, const VAccess& access) {
|
||||
AstVarRef* const refp = new AstVarRef{fl, varp, access};
|
||||
UASSERT_OBJ(m_modp, refp, "Must not nullptr");
|
||||
m_refsForPackedSplit[m_modp].add(refp);
|
||||
m_forPackedSplit.m_refs[m_modp].add(refp);
|
||||
return refp;
|
||||
}
|
||||
|
||||
|
@ -539,22 +544,26 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
}
|
||||
}
|
||||
void visit(AstVar* nodep) override {
|
||||
m_forPackedSplit.m_refs[m_modp].add(nodep);
|
||||
if (!nodep->attrSplitVar()) return; // Nothing to do
|
||||
if (!cannotSplitReason(nodep)) {
|
||||
m_refs.registerVar(nodep);
|
||||
UINFO(4, nodep->name() << " is added to candidate list.\n");
|
||||
}
|
||||
m_refsForPackedSplit[m_modp].add(nodep);
|
||||
}
|
||||
void visit(AstVarRef* nodep) override {
|
||||
m_forPackedSplit.m_refs[m_modp].add(nodep);
|
||||
if (!nodep->varp()->attrSplitVar()) return; // Nothing to do
|
||||
if (m_refs.tryAdd(m_contextp, nodep, m_inFTaskp)) {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
void visit(AstArraySel* nodep) override {
|
||||
|
@ -737,7 +746,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
connectPort(varp, vars, nullptr);
|
||||
}
|
||||
varp->attrSplitVar(!cannotSplitPackedVarReason(varp));
|
||||
m_refsForPackedSplit[m_modp].add(varp);
|
||||
m_forPackedSplit.m_refs[m_modp].add(varp);
|
||||
} else {
|
||||
pushDeletep(varp->unlinkFrBack());
|
||||
}
|
||||
|
@ -764,9 +773,9 @@ public:
|
|||
}
|
||||
~SplitUnpackedVarVisitor() override {
|
||||
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.
|
||||
// Even if this function returns true, the variable may not be split
|
||||
|
@ -954,19 +963,27 @@ public:
|
|||
};
|
||||
|
||||
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;
|
||||
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.
|
||||
std::map<AstVar*, PackedVarRef, AstNodeComparator> m_refs;
|
||||
void visit(AstNodeFTask* nodep) override {
|
||||
if (!cannotSplitTaskReason(nodep)) iterateChildren(nodep);
|
||||
}
|
||||
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)) {
|
||||
warnNoSplit(nodep, nodep, reason);
|
||||
nodep->attrSplitVar(false);
|
||||
if (nodep->attrSplitVar()) {
|
||||
warnNoSplit(nodep, nodep, reason);
|
||||
nodep->attrSplitVar(false);
|
||||
}
|
||||
} else { // Finally find a good candidate
|
||||
const bool inserted = m_refs.emplace(nodep, PackedVarRef{nodep}).second;
|
||||
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);
|
||||
const auto refit = m_refs.find(varp);
|
||||
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,
|
||||
"variable in package must have been dropped beforehand.");
|
||||
const AstBasicDType* const basicp = refit->second.basicp();
|
||||
|
@ -999,7 +1016,7 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
iterateChildren(nodep);
|
||||
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
|
||||
= {{VN_CAST(nodep->lsbp(), Const),
|
||||
|
@ -1013,7 +1030,10 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
<< " [" << consts[0]->toSInt() << ":+" << consts[1]->toSInt()
|
||||
<< "] lsb:" << refit->second.basicp()->lo() << "\n");
|
||||
} 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]) {
|
||||
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");
|
||||
}
|
||||
m_refs.erase(varp);
|
||||
varp->attrSplitVar(false);
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
|
@ -1169,6 +1188,12 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
if (vars.size() == 1 && vars.front().bitwidth() == varp->width())
|
||||
continue; // No split
|
||||
|
||||
if (varp->attrSplitVar()) {
|
||||
++m_numSplitAttr;
|
||||
} else {
|
||||
++m_numSplitAuto;
|
||||
}
|
||||
|
||||
createVars(varp, ref.basicp(), vars); // Add the split variables
|
||||
|
||||
updateReferences(varp, ref, vars);
|
||||
|
@ -1191,18 +1216,114 @@ class SplitPackedVarVisitor final : public VNVisitor, public SplitVarImpl {
|
|||
} else { // the original variable is not used anymore.
|
||||
VL_DO_DANGLING(varp->unlinkFrBack()->deleteTree(), varp);
|
||||
}
|
||||
++m_numSplit;
|
||||
}
|
||||
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:
|
||||
// When reusing the information from SplitUnpackedVarVisitor
|
||||
SplitPackedVarVisitor(AstNetlist* nodep, SplitVarRefsMap& refs)
|
||||
SplitPackedVarVisitor(AstNetlist* nodep, SplitVarRefs fromUnpackedSplit)
|
||||
: m_netp{nodep} {
|
||||
// If you want ignore refs and walk the tne entire AST,
|
||||
// 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;
|
||||
i.second.visit(this);
|
||||
split();
|
||||
|
@ -1211,7 +1332,8 @@ public:
|
|||
}
|
||||
~SplitPackedVarVisitor() override {
|
||||
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.
|
||||
|
@ -1247,13 +1369,13 @@ const char* SplitVarImpl::cannotSplitPackedVarReason(const AstVar* varp) {
|
|||
|
||||
void V3SplitVar::splitVariable(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
SplitVarRefsMap refs;
|
||||
SplitVarRefs refs;
|
||||
{
|
||||
const SplitUnpackedVarVisitor visitor{nodep};
|
||||
refs = visitor.getPackedVarRefs();
|
||||
}
|
||||
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
||||
{ SplitPackedVarVisitor{nodep, refs}; }
|
||||
{ SplitPackedVarVisitor{nodep, std::move(refs)}; }
|
||||
V3Global::dumpCheckGlobalTree("split_var", 0, dumpTreeEitherLevel() >= 9);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@ import vltest_bootstrap
|
|||
|
||||
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()
|
||||
|
|
|
@ -11,9 +11,10 @@ import vltest_bootstrap
|
|||
|
||||
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 sigs deleted\s+(\d+)', 9)
|
||||
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0)
|
||||
|
||||
test.passes()
|
||||
|
|
|
@ -16,6 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test.
|
|||
test.execute()
|
||||
|
||||
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()
|
||||
|
|
|
@ -17,6 +17,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.t_dir + "/t_
|
|||
test.execute()
|
||||
|
||||
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()
|
||||
|
|
|
@ -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'
|
||||
17 | logic [1:0][3:0] array;
|
||||
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:18:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array'
|
||||
18 | logic [1:0][3:0] array;
|
||||
| ^~~~~
|
||||
t/t_order_blkandnblk_bad.v:19:25: ... Location of blocking assignment
|
||||
19 | always_comb array[0] = i;
|
||||
t/t_order_blkandnblk_bad.v:20:25: ... Location of blocking assignment
|
||||
20 | always_comb array[0] = i;
|
||||
| ^
|
||||
t/t_order_blkandnblk_bad.v:22:15: ... Location of nonblocking assignment
|
||||
22 | array[1] <= array[0];
|
||||
t/t_order_blkandnblk_bad.v:23:15: ... Location of nonblocking assignment
|
||||
23 | array[1] <= array[0];
|
||||
| ^~
|
||||
... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest
|
||||
%Error: Exiting due to
|
||||
|
|
|
@ -8,10 +8,11 @@ module t (/*AUTOARG*/
|
|||
// Outputs
|
||||
o,
|
||||
// Inputs
|
||||
clk, i
|
||||
clk, i, idx
|
||||
);
|
||||
input clk;
|
||||
input [3:0] i;
|
||||
input idx;
|
||||
output [3:0] o;
|
||||
|
||||
logic [1:0][3:0] array;
|
||||
|
@ -21,6 +22,6 @@ module t (/*AUTOARG*/
|
|||
always @ (posedge clk)
|
||||
array[1] <= array[0];
|
||||
|
||||
assign o = array[1];
|
||||
assign o = array[idx];
|
||||
|
||||
endmodule
|
||||
|
|
|
@ -19,6 +19,7 @@ test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt"],
|
|||
|
||||
test.execute()
|
||||
|
||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\s+(\d+)', 15)
|
||||
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 automatically\s+(\d+)', 0)
|
||||
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()
|
||||
|
|
|
@ -21,7 +21,7 @@ test.compile(verilator_flags2=['--cc --trace --stats +define+TEST_ATTRIBUTES'],
|
|||
test.execute()
|
||||
|
||||
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+Split unpacked arrays\s+(\d+)', 27)
|
||||
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+unpacked arrays split due to attribute\s+(\d+)', 27)
|
||||
|
||||
test.passes()
|
||||
|
|
|
@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--stats'])
|
|||
|
||||
test.execute()
|
||||
|
||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\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+packed variables split due to attribute\s+(\d+)', 0)
|
||||
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 3)
|
||||
|
||||
test.passes()
|
||||
|
|
|
@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1'])
|
|||
|
||||
test.execute()
|
||||
|
||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\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+packed variables split due to attribute\s+(\d+)', 1)
|
||||
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 0)
|
||||
|
||||
test.passes()
|
||||
|
|
|
@ -16,7 +16,7 @@ test.compile(verilator_flags2=['--stats'])
|
|||
|
||||
test.execute()
|
||||
|
||||
test.file_grep(test.stats, r'SplitVar,\s+Split packed variables\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+packed variables split due to attribute\s+(\d+)', 0)
|
||||
test.file_grep(test.stats, r'SplitVar,\s+unpacked arrays split due to attribute\s+(\d+)', 0)
|
||||
|
||||
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