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

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

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("-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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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