Support multi-dim interface instance arrays and interface array ports (#8493)

Companion to #8476, and this now moves all instance array and interface
array/modport array port expansion into elaboration. Fixes #2675.

Arrays of interface instances and interface array ports are now split
into their elements during elaboration, instead of after V3Width in
V3Inst::dearrayAll, which is removed.

Background: next to every interface instance 'intf ifs()', V3LinkCells
creates a companion variable 'ifs__Viftop' of type IfaceRefDType
referring to the instance, so the instantiating module can use the
interface as a value (in pin connections, virtual interface assignments,
arguments, ...) just like a module referring to it through an interface
port. For an array of instances 'intf ifs[N]()', this variable is an
unpacked array of IfaceRefDType, but an IfaceRefDType can only refer to
a single instance, so it must be split into a variable for each element,
and every use of the whole array replaced.

V3Param expands interface instance arrays like module instance arrays,
also splitting the __Viftop variable referencing the whole array into a
variable for each element cell ('ifs__BRA__i__KET____Viftop', the
companion of element cell 'ifs__BRA__i__KET__'), and splits each
interface array port into a variable for each element. The whole array
variables remain until all references to them are replaced, so V3LinkDot
can link hierarchical references to the correct specialization.

In V3Width, every reference to a whole interface array, including
hierarchical ones, is replaced by an AstInitArray of references to its
element variables, typed as the whole array, e.g. 'ifs' becomes
'{ifs[0], ifs[1], ...}. The AstInitArray keeps its own type, so its uses
are checked as those of the whole array. The whole array variables are
removed at the end of V3Width.

V3Width then treats a whole interface array replaced by an AstInitArray
as an ordinary unpacked array value:
- A constant select yields the element, a slice the selected elements.
- A pin of an element of a module instance array selects its part of an
  interface array connection like any other connection.
- A pin of an interface array port is split into a pin for each element
  port, connected left index to left index.
- A member of an element is referenced through the interface, as for
  any other interface.

This supports slices of interface arrays, and interface ports of
multi-dimensional module instance arrays connected to interface arrays.
This commit is contained in:
Geza Lore
2026-09-24 19:10:33 +01:00
committed by GitHub
parent 1aa5ab2e3a
commit 9e03fed56b
34 changed files with 793 additions and 873 deletions
+4
View File
@@ -208,6 +208,10 @@ string AstNode::dedotName(const string& namein) {
return pretty;
}
string AstNode::nameNoArray(const string& namein) {
return namein.substr(0, namein.find("__BRA__"));
}
string AstNode::vcdName(const string& namein) {
// VCD tracing expects space to separate hierarchy
// Dots are reserved for dots the user put in the name
+1
View File
@@ -658,6 +658,7 @@ public:
string origNameProtect() const; // origName with --protect-id applied
string shortName() const; // Name with __PVT__ removed for concatenating scopes
static string dedotName(const string& namein); // Name with dots removed
static string nameNoArray(const string& namein); // Name with any array index removed
static string prettyName(const string& namein) VL_PURE; // Name for printing out to the user
static string vpiName(const string& namein); // Name for vpi access
static string prettyNameQ(const string& namein) { // Quoted pretty name (for errors)
+6 -1
View File
@@ -700,7 +700,7 @@ class AstCell final : public AstNode {
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
// Pre V3Param: Range(s) for arrayed instances.
// From V3Param to V3Width: ranges of the arrayed cell this element cell was expanded from
// After V3Width: nullptr, except for interface arrays
// After V3Width: nullptr
// @astgen op3 := rangep : List[AstRange]
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing/VPI
//
@@ -2318,6 +2318,8 @@ class AstVar final : public AstNode {
bool m_isPulldown : 1; // Tri0
bool m_isPullup : 1; // Tri1
bool m_isIfaceParent : 1; // dtype is reference to interface present in this module
bool m_isIfaceArraySplit : 1; // Interface array that was split into element variables by
// V3Param, until V3Width removes it
bool m_isInternal : 1; // Internal state, don't add to method pinter
bool m_isIfaceParam : 1; // Parameter belongs to an interface/modport
bool m_isDpiOpenArray : 1; // DPI import open array
@@ -2382,6 +2384,7 @@ class AstVar final : public AstNode {
m_isPulldown = false;
m_isPullup = false;
m_isIfaceParent = false;
m_isIfaceArraySplit = false;
m_isInternal = false;
m_isIfaceParam = false;
m_isDpiOpenArray = false;
@@ -2543,6 +2546,7 @@ public:
void isContinuously(bool flag) { m_isContinuously = flag; }
void isStatic(bool flag) { m_isStatic = flag; }
void isIfaceParent(bool flag) { m_isIfaceParent = flag; }
void isIfaceArraySplit(bool flag) { m_isIfaceArraySplit = flag; }
void isInternal(bool flag) { m_isInternal = flag; }
void isIfaceParam(bool flag) { m_isIfaceParam = flag; }
void funcLocal(bool flag) {
@@ -2630,6 +2634,7 @@ public:
m_varType = VVarType::IFACEREF;
}
bool isIfaceParent() const { return m_isIfaceParent; }
bool isIfaceArraySplit() const { return m_isIfaceArraySplit; }
bool isInternal() const { return m_isInternal; }
bool isIfaceParam() const { return m_isIfaceParam; }
bool isSignal() const { return varType().isSignal(); }
+2
View File
@@ -3924,6 +3924,7 @@ void AstVar::dump(std::ostream& str) const {
if (isConst()) str << " [CONST]";
if (isPullup()) str << " [PULLUP]";
if (isPulldown()) str << " [PULLDOWN]";
if (isIfaceArraySplit()) str << " [IFACEARRAYSPLIT]";
if (isSigPublic()) str << " [P]";
if (isSigUserRdPublic()) str << " [PRD]";
if (isSigUserRWPublic()) str << " [PWR]";
@@ -3968,6 +3969,7 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, isConst);
dumpJsonBoolFuncIf(str, isPullup);
dumpJsonBoolFuncIf(str, isPulldown);
dumpJsonBoolFuncIf(str, isIfaceArraySplit);
dumpJsonBoolFuncIf(str, isSigPublic);
dumpJsonBoolFuncIf(str, isLatched);
dumpJsonBoolFuncIf(str, isUsedLoopIdx);
-666
View File
@@ -193,666 +193,6 @@ public:
~InstVisitor() override = default;
};
//######################################################################
class InstDeModVarVisitor final : public VNVisitorConst {
// Expand all module variables, and save names for later reference
private:
// STATE
std::map<const std::string, AstVar*> m_modVarNameMap; // Per module, name of cloned variables
// VISITORS
void visit(AstVar* nodep) override {
if (VN_IS(nodep->dtypep()->skipRefp(), IfaceRefDType)) {
UINFO(8, " dm-1-VAR " << nodep);
insert(nodep);
}
iterateChildrenConst(nodep);
}
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
// METHODS
void insert(AstVar* nodep) {
UINFO(8, " dmINSERT " << nodep);
m_modVarNameMap.emplace(nodep->name(), nodep);
}
AstVar* find(const string& name) {
const auto it = m_modVarNameMap.find(name);
if (it != m_modVarNameMap.end()) {
return it->second;
} else {
return nullptr;
}
}
void dump() {
for (const auto& itr : m_modVarNameMap) {
cout << "-namemap: " << itr.first << " -> " << itr.second << '\n';
}
}
// CONSTRUCTORS
InstDeModVarVisitor() = default;
~InstDeModVarVisitor() override = default;
void main(AstNodeModule* nodep) {
UINFO(8, " dmMODULE " << nodep);
m_modVarNameMap.clear();
iterateConst(nodep);
}
};
//######################################################################
// Replace any leftover VarRef to an interface-array var that the cell
// dearrayer just deleted. The dearrayer handles VarRefs in pin / array-select
// / array-assign contexts itself; this rewriter catches the rest (a class
// new() arg, a function call arg, etc) by rebuilding an array literal over
// the per-element vars. Runs once, only if anything was actually deleted.
class InstDeOrphanVisitor final : public VNVisitor {
// STATE
const std::unordered_map<AstVar*, std::vector<AstVar*>>& m_dearrayed;
static bool isDearrayerHandled(const AstNode* backp) {
// VarRefs under these are rewritten by the cell dearrayer itself.
return VN_IS(backp, Pin) || VN_IS(backp, ArraySel) || VN_IS(backp, SliceSel)
|| VN_IS(backp, NodeAssign);
}
// Build a (possibly nested) InitArray matching dtp's array nesting; leaves
// are VarRefs to perElem, consumed in flat row-major order.
static AstNodeExpr* buildPattern(FileLine* flp, AstNodeDType* dtp,
const std::vector<AstVar*>& perElem, size_t& idxr,
const VAccess& access) {
AstNodeDType* const skipDtp = dtp->skipRefp();
if (AstUnpackArrayDType* const arrp = VN_CAST(skipDtp, UnpackArrayDType)) {
AstInitArray* const initp = new AstInitArray{flp, arrp, nullptr};
const int elems = arrp->elementsConst();
for (int i = 0; i < elems; ++i) {
initp->addIndexValuep(static_cast<uint64_t>(i),
buildPattern(flp, arrp->subDTypep(), perElem, idxr, access));
}
return initp;
}
UASSERT(idxr < perElem.size(), "buildPattern outran per-element vars");
return new AstVarRef{flp, perElem[idxr++], access};
}
// VISITORS
void visit(AstVarRef* nodep) override {
const auto it = m_dearrayed.find(nodep->varp());
if (it == m_dearrayed.end()) return;
if (isDearrayerHandled(nodep->backp())) return;
size_t idx = 0;
AstNodeExpr* const newp = buildPattern(nodep->fileline(), it->first->dtypep(), it->second,
idx, nodep->access());
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
InstDeOrphanVisitor(AstNode* rootp,
const std::unordered_map<AstVar*, std::vector<AstVar*>>& dearrayed)
: m_dearrayed{dearrayed} {
iterate(rootp);
}
~InstDeOrphanVisitor() override = default;
};
//######################################################################
class InstDeVisitor final : public VNVisitor {
// Find all cells with arrays, and convert to non-arrayed
private:
// STATE
const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells
int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion
InstDeModVarVisitor m_deModVars; // State of variables for current cell module
// Iface-array vars deleted by visit(AstCell), mapped to their per-element
// replacements in row-major order. The post-pass uses this to fix up any
// leftover VarRefs (see InstDeOrphanVisitor).
std::unordered_map<AstVar*, std::vector<AstVar*>> m_dearrayedIfaceVars;
// VISITORS
void visit(AstVar* nodep) override {
// cppcheck-suppress constVariablePointer
AstNode* const dtp = nodep->dtypep()->skipRefp();
if (VN_IS(dtp, UnpackArrayDType)
&& VN_IS(VN_AS(dtp, UnpackArrayDType)->subDTypep()->skipRefp(), IfaceRefDType)) {
if (VN_AS(VN_AS(dtp, UnpackArrayDType)->subDTypep()->skipRefp(), IfaceRefDType)
->isVirtual())
return;
UINFO(8, " dv-vec-VAR " << nodep);
AstUnpackArrayDType* const arrdtype = VN_AS(dtp, UnpackArrayDType);
AstNode* prevp = nullptr;
for (int i = arrdtype->lo(); i <= arrdtype->hi(); ++i) {
const string varNewName = nodep->name() + "__BRA__" + cvtToStr(i) + "__KET__";
UINFO(8, "VAR name insert " << varNewName << " " << nodep);
if (!m_deModVars.find(varNewName)) {
AstIfaceRefDType* const ifaceRefp
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType)
->cloneTree(false);
arrdtype->addNextHere(ifaceRefp);
ifaceRefp->cellp(nullptr);
AstVar* const varNewp = nodep->cloneTree(false);
varNewp->name(varNewName);
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(i) + "__KET__");
varNewp->dtypep(ifaceRefp);
m_deModVars.insert(varNewp);
if (!prevp) {
prevp = varNewp;
} else {
prevp->addNextHere(varNewp);
}
}
}
if (prevp) nodep->addNextHere(prevp);
if (prevp && debug() == 9) {
prevp->dumpTree("- newintf: ");
cout << '\n';
}
}
iterateChildren(nodep);
}
void visit(AstCell* nodep) override {
UINFO(4, " CELL " << nodep);
// Find submodule vars
UASSERT_OBJ(nodep->modp(), nodep, "Unlinked");
m_deModVars.main(nodep->modp());
//
if (nodep->rangep()) {
// Only interface arrays, V3Param expanded other instance arrays
UASSERT_OBJ(VN_IS(nodep->modp(), Iface), nodep, "Unexpanded instance array");
// Collect the full range chain (outer first).
std::vector<const AstRange*> rangesp;
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
rangesp.push_back(rp);
}
m_cellRangep = rangesp.front();
const int ndim = static_cast<int>(rangesp.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = rangesp[d]->elementsConst();
totalElems *= sizes[d];
}
AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var);
// Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType.
AstIfaceRefDType* origIfaceRefp = nullptr;
AstUnpackArrayDType* innermostArrp = nullptr;
for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) {
if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) {
innermostArrp = arrp;
dp = arrp->subDTypep()->skipRefp();
} else {
origIfaceRefp = VN_CAST(dp, IfaceRefDType);
break;
}
}
const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual();
// Collected in row-major order for the orphan VarRef rewriter.
std::vector<AstVar*> perElemVarps;
if (isIface) perElemVarps.reserve(totalElems);
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
// Unflatten n into a row-major cartesian index; outer dim most significant.
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
// Flat select number for 1D-compat pin expansion; ascending dims invert.
// Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx).
int flatSel = 0;
string suffix;
for (int d = 0; d < ndim; ++d) {
const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d];
flatSel = flatSel * sizes[d] + sel;
suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d])
+ "__KET__";
}
m_instSelNum = flatSel;
AstCell* const newp = nodep->cloneTree(false);
nodep->addNextHere(newp);
while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
newp->name(newp->name() + suffix);
newp->origName(newp->origName() + suffix);
UINFO(8, " CELL loop " << newp);
// Interface instantiation: also clone the IfaceRef in the parent module.
if (isIface) {
// Cache the interface module on the dtype so ifaceViaCellp()
// still works after we clear cellp and delete the cell.
if (!origIfaceRefp->ifacep()) {
origIfaceRefp->ifacep(VN_AS(nodep->modp(), Iface));
}
origIfaceRefp->cellp(nullptr);
AstVar* const varNewp = ifaceVarp->cloneTree(false);
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
innermostArrp->addNextHere(ifaceRefp);
ifaceRefp->cellp(newp);
ifaceRefp->cellName(newp->name());
varNewp->name(varNewp->name() + suffix);
varNewp->origName(varNewp->origName() + suffix);
varNewp->dtypep(ifaceRefp);
newp->addNextHere(varNewp);
perElemVarps.emplace_back(varNewp);
if (debug() == 9) {
varNewp->dumpTree("- newintf: ");
cout << '\n';
}
}
// Fixup pins
iterateAndNextNull(newp->pinsp());
if (debug() == 9) {
newp->dumpTree("- newcell: ");
cout << '\n';
}
}
// Done. Delete original
m_cellRangep = nullptr;
if (isIface) {
// Hand the per-element vars to the orphan-VarRef rewriter.
if (!perElemVarps.empty()) {
m_dearrayedIfaceVars.emplace(ifaceVarp, std::move(perElemVarps));
}
ifaceVarp->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(ifaceVarp), ifaceVarp);
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
m_cellRangep = nullptr;
iterateChildren(nodep);
}
}
void visit(AstPin* nodep) override {
// Any non-direct pins need reconnection with a part-select
if (!nodep->exprp()) return; // No-connect
const AstNodeDType* expDtp = nodep->exprp()->dtypep()->skipRefp();
if (m_cellRangep) {
UINFO(4, " PIN " << nodep);
const int modwidth = nodep->modVarp()->width();
const int expwidth = nodep->exprp()->width();
const std::pair<uint32_t, uint32_t> pinDim
= nodep->modVarp()->dtypep()->skipRefp()->dimensions(false);
const std::pair<uint32_t, uint32_t> expDim = expDtp->dimensions(false);
UINFO(4, " PINVAR " << nodep->modVarp());
UINFO(4, " EXP " << nodep->exprp());
UINFO(4, " expwidth=" << expwidth << " modwidth=" << modwidth
<< " expDim(p,u)=" << expDim.first << "," << expDim.second
<< " pinDim(p,u)=" << pinDim.first << "," << pinDim.second);
if (expDim.second == pinDim.second + 1) {
// Connection to array, where array dimensions match the instant dimension
const AstRange* const rangep = VN_AS(expDtp, UnpackArrayDType)->rangep();
const int arraySelNum = rangep->ascending()
? (rangep->elementsConst() - 1 - m_instSelNum)
: m_instSelNum;
AstNodeExpr* exprp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack();
exprp = new AstArraySel{exprp->fileline(), exprp, arraySelNum};
nodep->exprp(exprp);
} else if (expwidth == modwidth) {
// NOP: Arrayed instants: widths match so connect to each instance
} else if (expwidth == modwidth * m_cellRangep->elementsConst()) {
// Arrayed instants: one bit for each of the instants (each
// assign is 1 modwidth wide)
if (m_cellRangep->ascending()) {
nodep->exprp()->v3warn(ASCRANGE, "Ascending instance range connecting to "
"vector: left < right of instance range: ["
<< m_cellRangep->leftConst() << ":"
<< m_cellRangep->rightConst() << "]");
}
AstNodeExpr* exprp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack();
exprp = new AstSel{exprp->fileline(), exprp, modwidth * m_instSelNum, modwidth};
nodep->exprp(exprp);
} else {
nodep->v3fatalSrc("Width mismatch; V3Width should have errored out.");
}
} // end expanding ranged cell
else if (AstArraySel* const arrselp = VN_CAST(nodep->exprp(), ArraySel)) {
if (const AstUnpackArrayDType* const arrp
= VN_CAST(arrselp->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
// Interface pin attaches to one element of arrayed interface
V3Const::constifyParamsEdit(arrselp->bitp());
const AstConst* const constp = VN_CAST(arrselp->bitp(), Const);
if (!constp) {
nodep->v3warn(
E_UNSUPPORTED,
"Unsupported: Non-constant index when passing interface to module");
return;
}
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo());
if (VN_IS(arrselp->fromp(), SliceSel))
arrselp->fromp()->v3warn(E_UNSUPPORTED, "Unsupported: interface slices");
const AstVarRef* const varrefp = VN_CAST(arrselp->fromp(), VarRef);
UASSERT_OBJ(varrefp, arrselp, "No interface varref under array");
AstVarXRef* const newp = new AstVarXRef{
nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "",
VAccess::WRITE};
newp->dtypep(nodep->modVarp()->dtypep());
newp->classOrPackagep(varrefp->classOrPackagep());
arrselp->addNextHere(newp);
VL_DO_DANGLING(arrselp->unlinkFrBack()->deleteTree(), arrselp);
}
} else {
AstVar* const pinVarp = nodep->modVarp();
// Multi-dim whole-array iface pin fanout: cartesian-product the port's
// nested UnpackArrayDType layers and emit one pin + per-element var per cell.
// For 1-dim falls through to the original code below.
std::vector<const AstUnpackArrayDType*> portArrs;
for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
portArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
if (portArrs.size() >= 2) {
AstIfaceRefDType* const portIrp
= VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType);
if (!portIrp || portIrp->isVirtual()) return;
const int ndim = static_cast<int>(portArrs.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = portArrs[d]->elementsConst();
totalElems *= sizes[d];
}
// V3Width should have already rejected non-VarRef and rank-mismatch pins;
// these are defensive internal-invariant guards.
const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef);
UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port");
std::vector<const AstUnpackArrayDType*> exprArrs;
for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
exprArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
UASSERT_OBJ(exprArrs.size() == static_cast<size_t>(ndim), nodep->exprp(),
"Multi-dim iface pin expression rank does not match port");
AstNode* prevp = nullptr;
AstNode* prevPinp = nullptr;
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
string portSuffix;
string exprSuffix;
for (int d = 0; d < ndim; ++d) {
portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d])
+ "__KET__";
exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d])
+ "__KET__";
}
const string varNewName = pinVarp->name() + portSuffix;
AstVar* varNewp = nullptr;
if (!pinVarp->backp()) {
varNewp = m_deModVars.find(varNewName);
} else {
portIrp->cellp(nullptr);
varNewp = pinVarp->cloneTree(false);
varNewp->name(varNewName);
varNewp->origName(varNewp->origName() + portSuffix);
varNewp->dtypep(portIrp);
m_deModVars.insert(varNewp);
prevp = AstNode::addNextNull(prevp, varNewp);
}
if (!varNewp) {
if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE
nodep->v3fatalSrc("Module dearray failed for "
<< AstNode::prettyNameQ(varNewName));
}
AstPin* const newp = nodep->cloneTree(false);
newp->modVarp(varNewp);
newp->name(newp->name() + portSuffix);
AstVarXRef* const newVarXRefp = new AstVarXRef{
nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE};
newVarXRefp->varp(newp->modVarp());
newp->exprp()->unlinkFrBack()->deleteTree();
newp->exprp(newVarXRefp);
prevPinp = AstNode::addNextNull(prevPinp, newp);
}
if (prevp) {
pinVarp->replaceWith(prevp);
VL_DO_DANGLING(pushDeletep(pinVarp), pinVarp);
}
nodep->replaceWith(prevPinp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
const AstUnpackArrayDType* const pinArrp
= VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType);
if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
// Arrayed pin/var attaches to arrayed submodule lower port/var, expand it
AstNode* prevp = nullptr;
AstNode* prevPinp = nullptr;
// Clone the var referenced by the pin, and clone each var referenced by the varref
// Clone pin varp:
for (int in = 0; in < pinArrp->elementsConst(); ++in) { // 0 = leftmost
const int i = pinArrp->left() + in * pinArrp->declRange().leftToRightInc();
const string varNewName = pinVarp->name() + "__BRA__" + cvtToStr(i) + "__KET__";
AstVar* varNewp = nullptr;
// Only clone the var once for all usages of a given child module
if (!pinVarp->backp()) {
varNewp = m_deModVars.find(varNewName);
} else {
AstIfaceRefDType* const ifaceRefp
= VN_AS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType);
ifaceRefp->cellp(nullptr);
varNewp = pinVarp->cloneTree(false);
varNewp->name(varNewName);
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(i) + "__KET__");
varNewp->dtypep(ifaceRefp);
m_deModVars.insert(varNewp);
prevp = AstNode::addNextNull(prevp, varNewp);
}
if (!varNewp) {
if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE
nodep->v3fatalSrc("Module dearray failed for "
<< AstNode::prettyNameQ(varNewName));
}
// But clone the pin for each module instance
// Now also clone the pin itself and update its varref
AstPin* const newp = nodep->cloneTree(false);
newp->modVarp(varNewp);
newp->name(newp->name() + "__BRA__" + cvtToStr(i) + "__KET__");
// And replace exprp with a new varxref
const AstVarRef* varrefp = VN_CAST(newp->exprp(), VarRef); // Maybe null
int expr_i = i;
if (const AstSliceSel* const slicep = VN_CAST(newp->exprp(), SliceSel)) {
varrefp = VN_AS(slicep->fromp(), VarRef);
UASSERT_OBJ(VN_IS(slicep->rhsp(), Const), slicep, "Slices should be constant");
const int slice_index
= slicep->declRange().left() + in * slicep->declRange().leftToRightInc();
const auto* const exprArrp
= VN_AS(varrefp->dtypep()->skipRefp(), UnpackArrayDType);
UASSERT_OBJ(exprArrp, slicep, "Slice of non-array");
expr_i = slice_index + exprArrp->lo();
} else if (!varrefp) {
newp->exprp()->v3error("Unexpected connection to arrayed port");
} else if (const auto* const exprArrp
= VN_CAST(varrefp->dtypep()->skipRefp(), UnpackArrayDType)) {
expr_i = exprArrp->left() + in * exprArrp->declRange().leftToRightInc();
}
const string newname = varrefp->name() + "__BRA__" + cvtToStr(expr_i) + "__KET__";
AstVarXRef* const newVarXRefp
= new AstVarXRef{nodep->fileline(), newname, "", VAccess::WRITE};
newVarXRefp->varp(newp->modVarp());
newp->exprp()->unlinkFrBack()->deleteTree();
newp->exprp(newVarXRefp);
prevPinp = AstNode::addNextNull(prevPinp, newp);
}
if (prevp) {
pinVarp->replaceWith(prevp);
VL_DO_DANGLING(pushDeletep(pinVarp), pinVarp);
} // else pinVarp already unlinked when another instance did this step
nodep->replaceWith(prevPinp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstArraySel* nodep) override {
// If a parent is also an ArraySel into the same iface array, let it handle the chain.
if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return;
// Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index).
std::vector<AstArraySel*> sels;
AstNode* curp = nodep;
while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) {
sels.push_back(asp);
curp = asp->fromp();
}
const AstVarRef* const varrefp = VN_CAST(curp, VarRef);
if (!varrefp) return;
// Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType.
std::vector<AstUnpackArrayDType*> arrs; // outer dim first
AstNodeDType* dtp = varrefp->dtypep()->skipRefp();
while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) {
arrs.push_back(ap);
dtp = ap->subDTypep()->skipRefp();
}
if (arrs.empty() || sels.size() != arrs.size()) return;
AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType);
if (!irp || irp->isVirtual()) return;
// Constify bitps and collect indices in outer-dim-first order (sels is inner-first).
std::vector<int> indices(sels.size());
for (size_t i = 0; i < sels.size(); ++i) {
AstArraySel* const asp = sels[i];
V3Const::constifyParamsEdit(asp->bitp());
const AstConst* const constp = VN_CAST(asp->bitp(), Const);
if (!constp) {
asp->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
return;
}
indices[sels.size() - 1 - i] = constp->toSInt();
}
string indexStr;
for (size_t i = 0; i < indices.size(); ++i) {
indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__";
}
AstMemberSel* const parentSelp = VN_CAST(nodep->backp(), MemberSel);
if (parentSelp && parentSelp->fromp() == nodep && parentSelp->varp()) {
AstVar* const memberVarp = parentSelp->varp();
AstVarXRef* const newp
= new AstVarXRef{parentSelp->fileline(), memberVarp->name(),
varrefp->name() + indexStr, parentSelp->access()};
newp->varp(memberVarp);
newp->dtypep(parentSelp->dtypep());
newp->classOrPackagep(varrefp->classOrPackagep());
parentSelp->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(parentSelp), parentSelp);
return;
}
AstVarXRef* const newp
= new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ};
newp->dtypep(irp);
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstNodeAssign* nodep) override {
if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) {
if (const AstUnpackArrayDType* const arrp
= VN_CAST(arrslicep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
arrslicep->v3warn(E_UNSUPPORTED, "Interface slices unsupported");
return;
}
} else {
if (const AstUnpackArrayDType* const rhsarrp
= VN_CAST(nodep->rhsp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (const AstUnpackArrayDType* const lhsarrp
= VN_CAST(nodep->lhsp()->dtypep()->skipRefp(), UnpackArrayDType)) {
// copy between arrays
if (!VN_IS(lhsarrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (!VN_IS(rhsarrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(rhsarrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual())
return;
if (!VN_AS(lhsarrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) {
nodep->v3warn(E_UNSUPPORTED, "Unexpected target of interface assignment ["
<< rhsarrp->prettyDTypeNameQ() << "]");
return;
}
if (lhsarrp->elementsConst() != rhsarrp->elementsConst()) {
nodep->v3warn(E_UNSUPPORTED,
"Array size mismatch in interface assignment");
return;
}
for (int i = 0; i < lhsarrp->elementsConst(); ++i) {
const string index = AstNode::encodeNumber(i);
AstNodeExpr* lhsp = nullptr;
if (AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) {
AstVarRef* const newvarp = varrefp->cloneTree(false);
AstArraySel* newarrselp = new AstArraySel{
nodep->fileline(), newvarp,
new AstConst{nodep->fileline(), static_cast<uint32_t>(i)}};
lhsp = newarrselp;
} else if (AstMemberSel* const prevselp
= VN_CAST(nodep->lhsp(), MemberSel)) {
AstMemberSel* membselp = prevselp->cloneTree(false);
AstArraySel* newarrselp = new AstArraySel{
nodep->fileline(), membselp,
new AstConst{nodep->fileline(), static_cast<uint32_t>(i)}};
lhsp = newarrselp;
} else {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported LHS node type in array assignment");
return;
}
const AstVarRef* const rhsrefp = VN_CAST(nodep->rhsp(), VarRef);
AstVarXRef* const rhsp = new AstVarXRef{
nodep->fileline(), rhsrefp->name() + "__BRA__" + index + "__KET__", "",
VAccess::READ};
rhsp->dtypep(rhsarrp->subDTypep()->skipRefp());
rhsp->classOrPackagep(rhsrefp->classOrPackagep());
AstAssign* const assignp = new AstAssign{nodep->fileline(), lhsp, rhsp};
nodep->addNextHere(assignp);
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
}
}
iterateChildren(nodep);
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
void visit(AstNew* nodep) override { iterateChildren(nodep); }
void visit(AstMethodCall* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit InstDeVisitor(AstNetlist* nodep) {
iterate(nodep);
// Skip when nothing was deleted; designs without iface arrays pay nothing.
if (!m_dearrayedIfaceVars.empty()) { InstDeOrphanVisitor{nodep, m_dearrayedIfaceVars}; }
}
~InstDeVisitor() override = default;
};
//######################################################################
// Inst static function
@@ -1005,9 +345,3 @@ void V3Inst::instAll(AstNetlist* nodep) {
{ InstVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("inst", 0, dumpTreeEitherLevel() >= 3);
}
void V3Inst::dearrayAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ InstDeVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("dearray", 0, dumpTreeEitherLevel() >= 6);
}
-1
View File
@@ -30,7 +30,6 @@ class AstPin;
class V3Inst final {
public:
static void instAll(AstNetlist* nodep) VL_MT_DISABLED;
static void dearrayAll(AstNetlist* nodep) VL_MT_DISABLED;
static AstAssignW* pinReconnectSimple(AstPin* pinp, AstCell* cellp, bool forTristate,
bool alwaysCvt = false) VL_MT_DISABLED;
static void checkOutputShort(const AstPin* nodep) VL_MT_DISABLED;
-1
View File
@@ -780,7 +780,6 @@ class LinkCellsVisitor final : public VNVisitor {
idtypep->cellp(nodep); // Only set when real parent cell known.
AstVar* varp;
if (nodep->rangep()) {
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst.
// Multi-dim arrays wrap one UnpackArrayDType per range, innermost first.
std::vector<AstRange*> rangesp;
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
+7 -5
View File
@@ -540,13 +540,14 @@ AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp,
component = remaining.substr(0, dotPos);
remaining = remaining.substr(dotPos + 1);
}
const size_t braPos = component.find("__BRA__");
const string componentBase
= (braPos == string::npos) ? component : component.substr(0, braPos);
// Matched without any array index; the elements of an instance array (see V3Param) all
// instantiate the same module, so any of them will do
const string componentBase = AstNode::nameNoArray(component);
AstNodeModule* nextModp = nullptr;
for (AstNode* sp = curModp->stmtsp(); sp; sp = sp->nextp()) {
if (AstCell* const cellp = VN_CAST(sp, Cell)) {
if ((cellp->name() == component || cellp->name() == componentBase)
if ((cellp->name() == component
|| AstNode::nameNoArray(cellp->name()) == componentBase)
&& cellp->modp()) {
nextModp = cellp->modp();
break;
@@ -559,7 +560,8 @@ AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp,
if (viftopPos != string::npos) {
varBaseName = varBaseName.substr(0, viftopPos);
}
if (varBaseName == component || varBaseName == componentBase) {
if (varBaseName == component
|| AstNode::nameNoArray(varBaseName) == componentBase) {
if (AstIfaceRefDType* const irefp
= ifaceRefFromVarDType(varp->subDTypep())) {
AstIface* const ifacep = irefp->ifaceViaCellp();
-53
View File
@@ -357,59 +357,6 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
pinp->modVarp(oldvarp);
cellp->addPinsp(pinp);
}
} else if (VN_IS(subtypep, UnpackArrayDType)) {
const AstUnpackArrayDType* const oldarrp
= VN_AS(subtypep, UnpackArrayDType);
const AstNodeDType* const arrsubtypep = oldarrp->subDTypep();
if (VN_IS(arrsubtypep, IfaceRefDType)) {
const AstIfaceRefDType* const ifacerefp
= VN_AS(arrsubtypep, IfaceRefDType);
if (!ifacerefp->cellp()) {
string name = oldvarp->name();
if (dupNames.find(name) != dupNames.end()) {
// __02E=. while __DOT__ looks nicer but will break V3LinkDot
name = oldmodp->name() + "__02E" + name;
}
AstUnpackArrayDType* arraydtypep
= VN_AS(oldvarp->dtypep(), UnpackArrayDType);
AstCell* ifacearraycellp
= new AstCell{newmodp->fileline(),
newmodp->fileline(),
name,
ifacerefp->ifaceName(),
nullptr,
nullptr,
arraydtypep->rangep()->cloneTree(true)};
ifacearraycellp->modp(ifacerefp->ifacep());
newmodp->addStmtsp(ifacearraycellp);
AstIfaceRefDType* const idtypep = new AstIfaceRefDType{
newmodp->fileline(), name, ifacerefp->ifaceName()};
idtypep->ifacep(nullptr);
idtypep->dtypep(idtypep);
idtypep->cellp(ifacearraycellp);
rootp->typeTablep()->addTypesp(idtypep);
AstNodeArrayDType* const arrp = new AstUnpackArrayDType{
newmodp->fileline(), idtypep,
arraydtypep->rangep()->cloneTree(true)};
AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF,
name + "__Viftop", arrp};
varp->isIfaceParent(true);
ifacearraycellp->addNextHere(varp);
ifacearraycellp->hasIfaceVar(true);
rootp->typeTablep()->addTypesp(arrp);
AstPin* const pinp = new AstPin{
oldvarp->fileline(), 0, varp->name(),
new AstVarRef{varp->fileline(), varp,
oldvarp->isWritable() ? VAccess::WRITE
: VAccess::READ}};
pinp->modVarp(oldvarp);
cellp->addPinsp(pinp);
}
}
}
}
}
+110 -31
View File
@@ -40,9 +40,10 @@
// Then process all modules called by that cell.
// (Cells never referenced after parameters expanded must be ignored.)
//
// Arrayed instances of modules are expanded into their elements before
// the above, each element knowing its position in the array (for V3Width
// to select its part of the port connections).
// Arrayed instances of modules and interfaces are expanded into their
// elements before the above, each element knowing its position in the
// array (for V3Width to select its part of the port connections).
// Interface array ports are split into a variable for each element.
//
// After we complete parameters, the varp's will be wrong (point to old module)
// and must be relinked.
@@ -855,10 +856,10 @@ class ParamProcessor final {
const size_t lastDot = cellPath.rfind('.');
const string lastComp
= (lastDot == string::npos) ? cellPath : cellPath.substr(lastDot + 1);
const size_t braPos = lastComp.find("__BRA__");
const string lastCompBase
= (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos);
if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false;
if (lastComp != cloneCellp->name()
&& AstNode::nameNoArray(lastComp) != cloneCellp->name()) {
return false;
}
// No parent portion to verify - startModp itself must be the expected parent
if (lastDot == string::npos) return startModp == expectModp;
const string parentPath = cellPath.substr(0, lastDot);
@@ -1832,9 +1833,14 @@ class ParamProcessor final {
= VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType);
}
}
// Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array).
// Pin is a select, of any depth, of a VarRef (e.g. SelBit/ArraySel into an iface
// array, or a slice of a row of a multi-dimensional one)
if (!pinIrefp && exprp) {
if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) {
const AstNode* basep = exprp;
while (const AstNodePreSel* const selp = VN_CAST(basep, NodePreSel)) {
basep = selp->fromp();
}
if (const AstVarRef* const vrp = VN_CAST(basep, VarRef)) {
if (vrp->varp()) {
pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()),
IfaceRefDType);
@@ -3049,11 +3055,45 @@ class ParamVisitor final : public VNVisitor {
return false;
}
// Split interface array port 'portp' into a variable for each of its elements, in the
// dimensions of 'dtypep' inwards. 'suffix' is the name suffix of the dimensions outside
// 'dtypep'.
void expandIfaceArrayPortDimensions(AstVar* portp, AstNodeDType* dtypep,
const std::string& suffix) {
const AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType);
// Base case: add the element variable when no dimensions left
if (!arrp) {
AstVar* const varp = portp->cloneTree(false);
varp->name(portp->name() + suffix);
varp->origName(portp->origName() + suffix);
if (AstNodeDType* const oldp = varp->childDTypep()) {
VL_DO_DANGLING(pushDeletep(oldp->unlinkFrBack()), oldp);
}
varp->dtypep(nullptr);
varp->childDTypep(portp->subDTypep()->elemDTypep()->cloneTree(false));
portp->addNextHere(varp);
return;
}
// Enumerate the current dimension given by 'arrp'
// Each element is added right after 'portp', so go from right to left,
// to end with an enumeration from the left index to the right index.
const int left = arrp->left();
const int right = arrp->right();
const int step = arrp->declRange().ascending() ? 1 : -1;
for (int n = right; n != left - step; n -= step) {
const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(n) + "__KET__";
expandIfaceArrayPortDimensions(portp, arrp->subDTypep(), s);
}
}
// Add the elements of instance array 'arrayedCellp' in the dimensions from 'rangep' inwards.
// 'suffix' and 'idx' are the name suffix and row-major position (each dimension counted from
// the left) of the dimensions outside 'rangep'.
void expandCellArrayDimensions(AstCell* arrayedCellp, const AstRange* rangep,
const std::string& suffix, int idx) {
// the left) of the dimensions outside 'rangep'. For an array of interfaces, 'ifaceVarp' is
// the variable referencing the whole array (see V3LinkCells), which is also split into
// variables referencing the elements.
void expandCellArrayDimensions(AstCell* arrayedCellp, AstVar* ifaceVarp,
const AstRange* rangep, const std::string& suffix, int idx) {
// Base case: insert the element when no dimensions left
if (!rangep) {
AstCell* const elemp = arrayedCellp->cloneTree(false);
@@ -3061,6 +3101,22 @@ class ParamVisitor final : public VNVisitor {
elemp->origName(arrayedCellp->origName() + suffix);
elemp->arrayIdx(idx);
arrayedCellp->addNextHere(elemp);
if (ifaceVarp) {
AstIfaceRefDType* const irefp
= VN_AS(ifaceVarp->subDTypep()->elemDTypep(), IfaceRefDType)->cloneTree(false);
irefp->cellp(elemp);
irefp->cellName(elemp->name());
// Named like the variable of any interface cell (see V3LinkCells)
AstVar* const varp = ifaceVarp->cloneTree(false);
varp->name(elemp->name() + "__Viftop");
varp->origName(elemp->origName() + "__Viftop");
if (AstNodeDType* const oldp = varp->childDTypep()) {
VL_DO_DANGLING(pushDeletep(oldp->unlinkFrBack()), oldp);
}
varp->dtypep(nullptr);
varp->childDTypep(irefp);
ifaceVarp->addNextHere(varp);
}
return;
}
@@ -3074,7 +3130,7 @@ class ParamVisitor final : public VNVisitor {
const AstRange* const subRangep = VN_AS(rangep->nextp(), Range);
for (int n = right; n != left - step; n -= step) {
const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(n) + "__KET__";
expandCellArrayDimensions(arrayedCellp, subRangep, s, --idx);
expandCellArrayDimensions(arrayedCellp, ifaceVarp, subRangep, s, --idx);
}
}
@@ -3178,14 +3234,34 @@ class ParamVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstCell* nodep) override {
// Interface arrays are still expanded in V3Inst
if (nodep->rangep() && nodep->arrayIdx() < 0 && !VN_IS(nodep->modp(), Iface)) {
// Expand the instance array into its elements
if (nodep->rangep() && nodep->arrayIdx() < 0) {
// Type check the ranges
for (AstRange* rangep = nodep->rangep(); rangep;
rangep = VN_AS(rangep->nextp(), Range)) {
rangep = VN_AS(V3Width::widthParamsEdit(rangep), Range);
}
expandCellArrayDimensions(nodep, nodep->rangep(), "", 0);
// If it's an interface, pick up the corresponding AstIfaceRefDType variable
AstVar* const ifaceVarp = [&]() -> AstVar* {
if (!VN_IS(nodep->modp(), Iface)) return nullptr;
// The variable referencing the whole array, right after the cell (see V3LinkCells)
AstVar* const varp = VN_CAST(nodep->nextp(), Var);
UASSERT_OBJ(varp && varp->name() == nodep->name() + "__Viftop", nodep,
"No __Viftop variable for interface array");
return varp;
}();
// Expand the instance array into its elements
expandCellArrayDimensions(nodep, ifaceVarp, nodep->rangep(), "", 0);
if (ifaceVarp) {
ifaceVarp->isIfaceArraySplit(true);
// Until V3Width removes it, the whole array variable refers to the first
// element, which is also used for accessing the parameters of the array
AstCell* const firstp = VN_AS(nodep->nextp(), Cell);
AstIfaceRefDType* const irefp
= VN_AS(ifaceVarp->subDTypep()->elemDTypep(), IfaceRefDType);
irefp->cellp(firstp);
irefp->cellName(firstp->name());
m_ifaceInstCells.emplace(nodep->name(), firstp);
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
// The elements replaced the arrayed cell in place, so will be iterated next.
return;
@@ -3283,6 +3359,15 @@ class ParamVisitor final : public VNVisitor {
// Make sure all parameters are constantified
void visit(AstVar* nodep) override {
if (nodep->user2SetOnce()) return; // Process once
// Interface array port: split it into its elements
if (nodep->isIfaceRef() && !nodep->isIfaceParent()
&& (VN_IS(nodep->subDTypep()->skipRefp(), UnpackArrayDType)
|| VN_IS(nodep->subDTypep()->skipRefp(), BracketArrayDType))) {
// Also converts any C-style [N] dimensions, the ranges must be constant
V3Width::widthParamsEdit(nodep->subDTypep());
expandIfaceArrayPortDimensions(nodep, nodep->subDTypep(), "");
nodep->isIfaceArraySplit(true);
}
// Build cache of interface port names as we encounter them
if (nodep->isIfaceRef()) { m_ifacePortNames.insert(nodep->name()); }
iterateChildren(nodep);
@@ -3398,6 +3483,11 @@ class ParamVisitor final : public VNVisitor {
nodep->varp(nullptr);
return;
}
// Parameters of an interface array are accessed through its first element
if (nodep->varp() && nodep->varp()->isParam()) {
const auto it = m_ifaceInstCells.find(nodep->dotted());
if (it != m_ifaceInstCells.end()) nodep->dotted(it->second->name());
}
// Check to see if the scope is just an interface because interfaces are special
const string dotted = nodep->dotted();
if (!dotted.empty() && nodep->varp() && nodep->varp()->isParam()) {
@@ -3409,20 +3499,9 @@ class ParamVisitor final : public VNVisitor {
}
if (const AstVar* const varp = VN_CAST(backp, Var)) {
if (!varp->isIfaceRef()) continue;
const AstIfaceRefDType* ifacerefp = nullptr;
if (const AstNodeDType* const typep = varp->childDTypep()) {
ifacerefp = VN_CAST(typep, IfaceRefDType);
if (!ifacerefp) {
if (VN_IS(typep, UnpackArrayDType)) {
ifacerefp = VN_CAST(typep->getChildDTypep(), IfaceRefDType);
}
}
if (!ifacerefp) {
if (VN_IS(typep, BracketArrayDType)) {
ifacerefp = VN_CAST(typep->subDTypep(), IfaceRefDType);
}
}
}
// Through all dimensions of an array
const AstIfaceRefDType* const ifacerefp
= VN_CAST(varp->subDTypep()->elemDTypep(), IfaceRefDType);
if (!ifacerefp) continue;
// Interfaces passed in on the port map have ifaces
if (const AstIface* const ifacep = ifacerefp->ifacep()) {
+266 -37
View File
@@ -272,6 +272,7 @@ class WidthVisitor final : public VNVisitor {
AstNode* m_seqUnsupp = nullptr; // Property has unsupported node
bool m_hasSExpr = false; // Property has a sequence expression
const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations
std::vector<AstVar*> m_splitIfaceArrayVarps; // Interface arrays split by V3Param
const AstEnumItem* m_enumItemp = nullptr; // Current enum item
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
AstNodeModule* m_modep = nullptr; // Current module
@@ -1301,6 +1302,26 @@ class WidthVisitor final : public VNVisitor {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "Bit select", nodep->bitp(), SELF, BOTH);
userIterateAndNext(nodep->fromp(), WidthVP{SELF, BOTH}.p());
// Element of a whole array of interfaces
if (VN_IS(nodep->fromp(), InitArray) && isIfaceArrayDType(nodep->fromp()->dtypep())) {
V3Const::constifyParamsEdit(nodep->bitp()); // May relink pointed to node
if (const AstConst* const constp = VN_CAST(nodep->bitp(), Const)) {
const int bit = constp->toSInt();
AstNodeExpr* newp = newIfaceArrayElement(nodep->fromp(), bit);
if (!newp) {
// No such instance, so an error, unlike other out of range selects
const AstUnpackArrayDType* const arrp
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType);
nodep->v3error("Selection index out of range of interface array: "
<< bit + arrp->lo() << " outside " << arrp->hi() << ":"
<< arrp->lo() << " (IEEE 1800-2023 23.6)");
newp = newIfaceArrayElement(nodep->fromp(), 0);
}
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
//
int frommsb;
int fromlsb;
@@ -1482,6 +1503,19 @@ class WidthVisitor final : public VNVisitor {
<< adtypep->declRange() << "'");
}
}
// A slice of a whole array of interfaces is the array of the selected elements
if (VN_IS(nodep->fromp(), InitArray) && isIfaceArrayDType(nodep->fromp()->dtypep())) {
AstInitArray* const newp = new AstInitArray{nodep->fileline(), newDtp, nullptr};
const VNumRange range = nodep->declRange();
for (int n = 0; n < range.elements(); ++n) {
AstNodeExpr* ep = newIfaceArrayElement(nodep->fromp(), range.lo() + n);
// If out of range, reported above, so use any element to continue
if (!ep) ep = newIfaceArrayElement(nodep->fromp(), 0);
newp->addIndexValuep(n, ep);
}
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
@@ -3172,6 +3206,8 @@ class WidthVisitor final : public VNVisitor {
// Make sure dtype is sized
nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->subDTypep()));
UASSERT_OBJ(nodep->dtypep(), nodep, "No dtype determined for var");
// Removed at the end, see mainAcceptEdit
if (!m_paramsOnly && nodep->isIfaceArraySplit()) m_splitIfaceArrayVarps.push_back(nodep);
if (nodep->attrsp()) {
nodep->attrsp()->foreach([this, nodep](AstAttrOf* attrp) {
if (attrp->attrType() == VAttrType::VAR_PORT_DTYPE) {
@@ -3345,6 +3381,10 @@ class WidthVisitor final : public VNVisitor {
// Var hasn't been widthed, so make it so.
userIterate(nodep->varp(), nullptr);
}
if (!m_paramsOnly && nodep->varp()->isIfaceArraySplit()) {
replaceSplitIfaceArrayRef(nodep);
return;
}
// UINFOTREE(9, nodep, "", "VRin");
// UINFOTREE(9, nodep->varp(), "", "forvar");
// Note genvar's are also entered as integers
@@ -3661,7 +3701,8 @@ class WidthVisitor final : public VNVisitor {
assertAtExpr(nodep);
if (m_vup->prelim()) { // First stage evaluation
AstNodeDType* const vdtypep = m_vup->dtypeNullp();
if (!nodep->dtypep() || vdtypep) {
// A whole array of interfaces keeps its type, for its connections to be checked
if (!nodep->dtypep() || (vdtypep && !isIfaceArrayDType(nodep->dtypep()))) {
UASSERT_OBJ(vdtypep, nodep,
"InitArray type not assigned by AstPattern/Var visitor");
nodep->dtypep(vdtypep);
@@ -4095,10 +4136,22 @@ class WidthVisitor final : public VNVisitor {
foundp = clockingp->ensureEventp();
if (AstVar* const varp = VN_CAST(foundp, Var)) {
if (!varp->didWidth()) userIterate(varp, nullptr);
nodep->dtypep(foundp->dtypep());
nodep->varp(varp);
AstIface* const ifacep = adtypep->ifaceViaCellp();
varp->sensIfacep(ifacep);
// Member of a non-virtual interface reference, e.g. an element selected
// from a whole array of interfaces: reference it through the interface,
// as V3LinkDot does for 'iface.member'
const AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef);
if (refp && !adtypep->isVirtual() && refp->varp()->isIfaceRef()) {
AstVarXRef* const newp = new AstVarXRef{nodep->fileline(), varp,
refp->name(), nodep->access()};
newp->didWidth(true);
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
nodep->dtypep(foundp->dtypep());
nodep->varp(varp);
nodep->didWidth(true);
return;
}
@@ -6628,6 +6681,16 @@ class WidthVisitor final : public VNVisitor {
userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p());
UASSERT_OBJ(nodep->lhsp()->dtypep(), nodep, "How can LHS be untyped?");
UASSERT_OBJ(nodep->lhsp()->dtypep()->widthSized(), nodep, "How can LHS be unsized?");
// An interface instance, or an array or slice of them, is not a variable
if (const AstIfaceRefDType* const irefp
= VN_CAST(nodep->lhsp()->dtypep()->elemDTypep(), IfaceRefDType)) {
if (!irefp->isVirtual()) {
nodep->v3error(
"Illegal assignment to an interface instance (IEEE 1800-2023 10.4)");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
}
nodep->dtypeFrom(nodep->lhsp());
//
// AstPattern needs to know the proposed data type of the lhs, so pass on the prelim
@@ -7186,9 +7249,14 @@ class WidthVisitor final : public VNVisitor {
// Connection is self-determined
userIterateAndNext(nodep->exprp(), WidthVP{conDTypep, FINAL, STREAM_USE_ASSIGN}.p());
AstNodeExpr* const fromp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack();
AstArraySel* const selp = new AstArraySel{fromp->fileline(), fromp, bit};
selp->didWidth(true);
nodep->exprp(selp);
if (AstNodeExpr* const elemp = newIfaceArrayElement(fromp, bit)) {
nodep->exprp(elemp);
VL_DO_DANGLING(pushDeletep(fromp), fromp);
} else {
AstArraySel* const selp = new AstArraySel{fromp->fileline(), fromp, bit};
selp->didWidth(true);
nodep->exprp(selp);
}
return pinInstArrayElement(nodep, subRangep, idx % subElems);
}
@@ -7256,6 +7324,7 @@ class WidthVisitor final : public VNVisitor {
userIterate(nodep->modVarp(), nullptr);
}
if (!nodep->exprp()) { // No-connect
if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep);
return;
}
// Very much like like an assignment, but which side is LH/RHS
@@ -7273,6 +7342,18 @@ class WidthVisitor final : public VNVisitor {
UASSERT_OBJ(conDTypep, nodep, "Unlinked pin data type");
modDTypep = modDTypep->skipRefp();
conDTypep = conDTypep->skipRefp();
// An interface port, or an array of them, needs interface instances, not virtual
// interfaces
if (const AstIfaceRefDType* const modIrefp
= VN_CAST(modDTypep->elemDTypep(), IfaceRefDType)) {
const AstIfaceRefDType* const conIrefp
= VN_CAST(conDTypep->elemDTypep(), IfaceRefDType);
if (!modIrefp->isVirtual() && conIrefp && conIrefp->isVirtual()) {
nodep->v3error("Illegal " << nodep->prettyOperatorName()
<< ", interface port connected to a virtual"
" interface (IEEE 1800-2023 23.3.3.4)");
}
}
AstNodeDType* subDTypep = modDTypep;
const int modwidth = modDTypep->width();
int conwidth = conDTypep->width();
@@ -7280,34 +7361,14 @@ class WidthVisitor final : public VNVisitor {
|| similarDTypeRecurse(conDTypep, modDTypep)) {
userIterateAndNext(nodep->exprp(),
WidthVP{subDTypep, FINAL, STREAM_USE_ASSIGN}.p());
} else if (m_cellp->rangep() && m_cellp->arrayIdx() < 0) {
// Interface instance array, expanded later in V3Inst
const int numInsts = m_cellp->rangep()->elementsConst();
if (conwidth == modwidth) {
// Arrayed instants: widths match so connect to each instance
subDTypep = conDTypep; // = same expr dtype
} else if (conwidth == numInsts * modwidth) {
// Arrayed instants: one bit for each of the instants (each
// assign is 1 modwidth wide)
subDTypep = conDTypep; // = same expr dtype (but numInst*pin_dtype)
} else {
// Must be a error according to spec
// (Because we need to know if to connect to one or all instants)
nodep->v3error(ucfirst(nodep->prettyOperatorName())
<< " as part of a module instance array" << " requires "
<< modwidth << " or " << modwidth * numInsts
<< " bits, but connection's "
<< nodep->exprp()->prettyTypeName() << " generates " << conwidth
<< " bits. (IEEE 1800-2023 23.3.3)");
subDTypep = conDTypep; // = same expr dtype
}
userIterateAndNext(nodep->exprp(),
WidthVP{subDTypep, FINAL, STREAM_USE_ASSIGN}.p());
} else {
if (m_cellp->arrayIdx() >= 0) {
// Element of an instance array, select its part of the connection,
// then check it like any other pin, unless that is all done already
if (pinInstArrayElement(nodep, m_cellp->rangep(), m_cellp->arrayIdx())) return;
if (pinInstArrayElement(nodep, m_cellp->rangep(), m_cellp->arrayIdx())) {
if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep);
return;
}
conDTypep = nodep->exprp()->dtypep()->skipRefp();
conwidth = conDTypep->width();
}
@@ -7352,16 +7413,14 @@ class WidthVisitor final : public VNVisitor {
// TODO Simple dtype checking, should be a more general check
const AstNodeArrayDType* const exprArrayp = VN_CAST(conDTypep, UnpackArrayDType);
const AstNodeArrayDType* const modArrayp = VN_CAST(modDTypep, UnpackArrayDType);
if (exprArrayp && modArrayp && VN_IS(exprArrayp->subDTypep(), IfaceRefDType)
&& exprArrayp->declRange().elements() != modArrayp->declRange().elements()) {
const int exprSize = exprArrayp->declRange().elements();
const int modSize = modArrayp->declRange().elements();
if (exprArrayp && modArrayp && VN_IS(conDTypep->elemDTypep(), IfaceRefDType)
&& unpackedArraySize(conDTypep) != unpackedArraySize(modDTypep)) {
nodep->v3error("Illegal "
<< nodep->prettyOperatorName() << ","
<< " mismatch between port which is an interface array of size "
<< modSize << ","
<< unpackedArraySize(modDTypep) << ","
<< " and expression which is an interface array of size "
<< exprSize << ".");
<< unpackedArraySize(conDTypep) << ".");
UINFO(1, " Related lo: " << modDTypep);
UINFO(1, " Related hi: " << conDTypep);
} else if ((exprArrayp && !modArrayp) || (!exprArrayp && modArrayp)) {
@@ -7380,6 +7439,8 @@ class WidthVisitor final : public VNVisitor {
}
iterateCheckAssign(nodep, "pin connection", nodep->exprp(), FINAL, subDTypep);
}
// Interface array port: connect its elements
if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep);
}
// UINFOTREE(1, nodep, "", "PinOut");
}
@@ -10632,6 +10693,168 @@ class WidthVisitor final : public VNVisitor {
return nullptr;
}
//----------------------------------------------------------------------
// METHODS - interface arrays
// True if an unpacked array of non-virtual interfaces
static bool isIfaceArrayDType(const AstNodeDType* dtypep) {
if (!VN_IS(dtypep->skipRefp(), UnpackArrayDType)) return false;
const AstIfaceRefDType* const irefp = VN_CAST(dtypep->elemDTypep(), IfaceRefDType);
return irefp && !irefp->isVirtual();
}
// Number of elements of each unpacked dimension of 'dtypep', from the left, e.g. "2x3"
static std::string unpackedArraySize(const AstNodeDType* dtypep) {
std::string size;
dtypep = dtypep->skipRefp();
while (const AstUnpackArrayDType* const arrp = VN_CAST(dtypep, UnpackArrayDType)) {
if (!size.empty()) size += "x";
size += std::to_string(arrp->elementsConst());
dtypep = arrp->subDTypep()->skipRefp();
}
return size;
}
// A whole array of interfaces is an AstInitArray of references to its elements, so select
// its element 'bit' (counted from lo) directly. Returns nullptr if not such an array, or
// 'bit' is out of range.
static AstNodeExpr* newIfaceArrayElement(AstNodeExpr* fromp, int bit) {
AstInitArray* const initp = VN_CAST(fromp, InitArray);
if (!initp || !isIfaceArrayDType(initp->dtypep())) return nullptr;
const AstUnpackArrayDType* const arrp
= VN_AS(initp->dtypep()->skipRefp(), UnpackArrayDType);
if (bit < 0 || bit >= arrp->elementsConst()) return nullptr;
return initp->getIndexDefaultedValuep(bit)->cloneTree(false);
}
// References to the element variables of the whole interface array referenced by 'refp', in
// the dimensions of 'dtypep' inwards. 'name' is the element name so far, the element
// variables being named 'name' + 'suffix'. 'elemVarpr' is the element variable of the
// leftmost element in the dimensions of 'dtypep', and is moved to the right past them.
// E.g. 'ifc' of dimensions [0:1][3:2] -> '{'{ifc[0][3], ifc[0][2]}, '{ifc[1][3], ifc[1][2]}}
static AstNodeExpr* newIfaceArrayInit(const AstNodeVarRef* refp, AstNodeDType* dtypep,
const std::string& name, const std::string& suffix,
AstVar*& elemVarpr) {
FileLine* const flp = refp->fileline();
AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType);
// Base case: reference the element variable when no dimensions left
if (!arrp) {
AstVar* const varp = elemVarpr;
UASSERT_OBJ(varp && varp->name() == name + suffix, refp,
"Missing element variable of interface array " << name);
elemVarpr = VN_CAST(varp->nextp(), Var);
if (const AstVarXRef* const xrefp = VN_CAST(refp, VarXRef)) {
return new AstVarXRef{flp, varp, xrefp->dotted(), refp->access()};
}
return new AstVarRef{flp, varp, refp->access()};
}
// Enumerate the current dimension given by 'arrp', from the left index to the right,
// as the element variables are in that order, indexed from lo
AstInitArray* const initp = new AstInitArray{flp, arrp, nullptr};
const VNumRange range = arrp->declRange();
for (int n = 0, i = range.left(); n < range.elements(); ++n, i += range.leftToRightInc()) {
const std::string s = name + "__BRA__" + AstNode::encodeNumber(i) + "__KET__";
initp->addIndexValuep(
i - range.lo(), newIfaceArrayInit(refp, arrp->subDTypep(), s, suffix, elemVarpr));
}
return initp;
}
// Replace a reference to a whole interface array variable (a __Viftop companion, or an
// interface array port) split by V3Param, with the AstInitArray of references to its
// elements, which is how whole arrays of interfaces are handled from here on
void replaceSplitIfaceArrayRef(AstNodeVarRef* nodep) {
AstVar* const varp = nodep->varp();
UASSERT_OBJ(varp->isIfaceArraySplit(), nodep, "Not a split interface array");
std::string name = varp->name();
std::string suffix;
if (varp->isIfaceParent()) {
name = name.substr(0, name.rfind("__Viftop"));
suffix = "__Viftop";
}
// The element variables follow the whole array variable in order (see V3Param)
AstVar* elemVarp = VN_CAST(varp->nextp(), Var);
AstNodeExpr* const newp = newIfaceArrayInit(nodep, varp->dtypep(), name, suffix, elemVarp);
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
userIterate(newp, WidthVP{SELF, BOTH}.p());
}
// Add a pin for each element port of interface array port of pin 'nodep', split by V3Param,
// in the dimensions of 'dtypep' inwards, connected to the element at the same position,
// counted from the left, of 'conp', which is the corresponding (sub)array of the pin's
// connection, if any. 'suffix' is the name suffix of the dimensions outside 'dtypep'.
// 'elemVarpr' is the element port variable of the rightmost element in the dimensions of
// 'dtypep', and is moved to the left past them.
void expandIfaceArrayPinDimensions(AstPin* nodep, const AstNodeDType* dtypep,
AstNodeExpr* conp, const std::string& suffix,
AstVar*& elemVarpr) {
const AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType);
// Base case: add the pin of the element port after 'nodep' when no dimensions left
if (!arrp) {
UASSERT_OBJ(elemVarpr && elemVarpr->name() == nodep->modVarp()->name() + suffix, nodep,
"Missing element variable of interface array port " << suffix);
AstPin* const newp = nodep->cloneTree(false);
newp->name(nodep->name() + suffix);
newp->modVarp(elemVarpr);
elemVarpr = VN_CAST(elemVarpr->backp(), Var);
if (conp) newp->exprp(conp->cloneTree(false));
nodep->addNextHere(newp);
return;
}
// Enumerate the current dimension given by 'arrp'
// Each element is added right after 'nodep', so go from right to left,
// to end with an enumeration from the left index to the right index.
const VNumRange range = arrp->declRange();
for (int n = range.elements() - 1; n >= 0; --n) {
const int i = range.left() + n * range.leftToRightInc();
const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(i) + "__KET__";
// Element of the connection, if any, which is only cloned at the leaves
AstNodeExpr* const elemConnp = [&]() -> AstNodeExpr* {
if (!conp) return nullptr;
const AstUnpackArrayDType* const conArrp
= VN_AS(conp->dtypep()->skipRefp(), UnpackArrayDType);
const int elements = conArrp->elementsConst();
// Counted from lo
AstNodeExpr* const resultp
= VN_AS(conp, InitArray)
->getIndexDefaultedValuep(
conArrp->declRange().ascending() ? n : elements - 1 - n);
UASSERT_OBJ(resultp, conp, "Interface array connection of mismatched shape");
return resultp;
}();
expandIfaceArrayPinDimensions(nodep, arrp->subDTypep(), elemConnp, s, elemVarpr);
}
}
// Replace the given interface array port pin with a pin for each element port split by V3Param
void expandIfaceArrayPin(AstPin* nodep) {
AstVar* const portp = nodep->modVarp();
UASSERT_OBJ(portp->isIfaceArraySplit(), nodep, "Not a split interface array port");
// The element variables follow the port in order (see V3Param). Pins are added from the
// right, so start at the last.
AstVar* elemVarp = portp;
for (uint32_t n = portp->dtypep()->arrayUnpackedElements(); n; --n) {
elemVarp = VN_CAST(elemVarp->nextp(), Var);
UASSERT_OBJ(elemVarp, portp, "Missing element variables of interface array port");
}
// Remove the connection, so the element pins do not clone it all. Deletion is deferred,
// so its elements can still be cloned from.
AstNodeExpr* const exprp = VN_AS(nodep->exprp(), NodeExpr);
if (exprp) pushDeletep(exprp->unlinkFrBack());
// Connect nothing if not a whole array of interfaces of the shape of the port, which is
// reported elsewhere, so the element pins do not report it again
const bool connect
= exprp && isIfaceArrayDType(exprp->dtypep())
&& unpackedArraySize(exprp->dtypep()) == unpackedArraySize(portp->dtypep());
expandIfaceArrayPinDimensions(nodep, portp->dtypep(), connect ? exprp : nullptr, "",
elemVarp);
UASSERT_OBJ(elemVarp == portp, portp, "Mismatched element port variable");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
//----------------------------------------------------------------------
// METHODS - special iterators
// These functions save/restore the AstNUser information so it can pass to child nodes.
@@ -10682,7 +10905,13 @@ public:
, m_paramsOnly{paramsOnly}
, m_doGenerate{doGenerate} {}
AstNode* mainAcceptEdit(AstNode* nodep) {
return userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p());
nodep = userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p());
// All references to the whole interface array variables split by V3Param are now replaced
for (AstVar* const varp : m_splitIfaceArrayVarps) {
VL_DO_DANGLING(pushDeletep(varp->unlinkFrBack()), varp);
}
m_splitIfaceArrayVarps.clear();
return nodep;
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
+2 -2
View File
@@ -284,8 +284,8 @@ static void process() {
// should be after constifyAllLint() which flattens to 1D bit vector
V3SplitVar::splitVariable(v3Global.rootp());
// Remove interface arrays (must be between V3Width and scoping)
V3Inst::dearrayAll(v3Global.rootp());
// Nothing to relink, but LinkDot names blocks created since V3Width,
// e.g. by V3AssertNfa. TODO: get rid of this
V3LinkDot::linkDotArrayed(v3Global.rootp());
if (v3Global.opt.timing().isSetTrue()) {
+4 -1
View File
@@ -40,7 +40,10 @@ module Vt_debug_emitv_t;
disable label0;
end
endfunction
Iface the_ifaces[3:0] ();
Iface the_ifaces[3] ();
Iface the_ifaces[2] ();
Iface the_ifaces[1] ();
Iface the_ifaces[0] ();
initial begin
begin
if ((| $test$plusargs("HELLO"))) begin
-4
View File
@@ -74,12 +74,9 @@ for s in [
'Pattern replication value of 0 is not legal.',
'Signals inside functions/tasks cannot be marked forceable',
'Slice size cannot be zero.',
'Slices of arrays in assignments have different unpacked dimensions,',
'Symbol matching',
'Thread scheduler is unable to provide requested',
'Unexpected connection to arrayed port',
'Unsized numbers/parameters not allowed in streams.',
'Unsupported LHS node type in array assignment',
'Unsupported RHS tristate construct:',
'Unsupported or syntax error: Unsized range in instance or other declaration',
'Unsupported pullup/down (weak driver) construct.',
@@ -96,7 +93,6 @@ for s in [
'Unsupported: Default value on module inout/ref/constref:',
'Unsupported: Modport empty expression',
'Unsupported: Non-constant default value in missing argument',
'Unsupported: Non-constant index when passing interface to module',
'Unsupported: Only one PSL clock allowed per assertion',
'Unsupported: Public functions with >64 bit outputs;',
'Unsupported: Public functions with return > 64 bits wide.',
-10
View File
@@ -1,10 +0,0 @@
%Error-UNSUPPORTED: t/t_interface_ar3.v:18:18: Unsupported: interface slices
: ... note: In instance 't'
18 | .infc(iinst[0:1])
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Internal Error: t/t_interface_ar3.v:18:18: ../V3Inst.cpp:#: No interface varref under array
: ... note: In instance 't'
18 | .infc(iinst[0:1])
| ^
... This fatal error may be caused by the earlier error(s); resolve those first.
+2 -5
View File
@@ -11,11 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(
fails=test.vlt_all, # Verilator unsupported, bug546
expect_filename=test.golden_filename)
test.compile()
if not test.vlt_all:
test.execute()
test.execute()
test.passes()
+4
View File
@@ -7,4 +7,8 @@
: ... note: In instance 't'
23 | assign foos[bar].a = 1'b1;
| ^
%Error: t/t_interface_array_bad.v:23:20: Can't find definition of 'foos[??]'
: ... note: In instance 't'
23 | assign foos[bar].a = 1'b1;
| ^
%Error: Exiting due to
+1 -2
View File
@@ -1,8 +1,7 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Passing a real interface array as a class new() argument bound to a
// virtual interface array formal parameter. Exercises the cell dearrayer's
// orphan-VarRef fixup in V3Inst.
// virtual interface array formal parameter.
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
@@ -7,4 +7,8 @@
: ... note: In instance 't'
24 | ifaces[i].value = 8'(i);
| ^
%Error: t/t_interface_array_loop_bad.v:24:17: Can't find definition of 'ifaces[??]'
: ... note: In instance 't'
24 | ifaces[i].value = 8'(i);
| ^~~~~
%Error: Exiting due to
@@ -1,19 +1,19 @@
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:28:24: Ascending instance range connecting to vector: left < right of instance range: [0:2]
: ... note: In instance 't'
28 | foo_subm subs[N] (.x(X));
| ^
... For warning description see https://verilator.org/warn/ASCRANGE?v=latest
... Use "/* verilator lint_off ASCRANGE */" and lint_on around source to disable this message.
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:29:26: Ascending instance range connecting to vector: left < right of instance range: [1:3]
: ... note: In instance 't'
29 | foo_subm subl[1:3] (.x(X));
| ^
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:24:24: Ascending instance range connecting to vector: left < right of instance range: [0:2]
: ... note: In instance 't'
24 | foo_intf foos[N] (.x(X));
| ^
... For warning description see https://verilator.org/warn/ASCRANGE?v=latest
... Use "/* verilator lint_off ASCRANGE */" and lint_on around source to disable this message.
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:25:26: Ascending instance range connecting to vector: left < right of instance range: [1:3]
: ... note: In instance 't'
25 | foo_intf fool[1:3] (.x(X));
| ^
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:28:24: Ascending instance range connecting to vector: left < right of instance range: [0:2]
: ... note: In instance 't'
28 | foo_subm subs[N] (.x(X));
| ^
%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:29:26: Ascending instance range connecting to vector: left < right of instance range: [1:3]
: ... note: In instance 't'
29 | foo_subm subl[1:3] (.x(X));
| ^
%Error: Exiting due to
@@ -0,0 +1,5 @@
%Error: t/t_interface_array_size_bad.v:23:13: Slices of arrays in assignments have different unpacked dimensions, 6 versus 7
23 | c.vif = b;
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,25 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
interface A;
endinterface
typedef virtual A a_t;
typedef a_t a_array_t[6];
class C;
a_array_t vif;
endclass
module t;
A b[7] ();
C c;
initial begin
c = new();
c.vif = b;
end
endmodule
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--binary'])
test.execute()
test.passes()
@@ -0,0 +1,93 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
interface a_if;
int x;
endinterface
typedef virtual a_if va_t;
module sub;
a_if ifs[2] ();
for (genvar i = 0; i < 2; ++i) begin : g
initial ifs[i].x = 'h40 + i;
end
endmodule
class C;
va_t vif[2];
endclass
module t;
a_if asc[0:3] ();
a_if desc[3:0] ();
a_if a2d[1:0][0:2] ();
for (genvar i = 0; i < 4; ++i) begin : g_1d
initial begin
asc[i].x = 'h10 + i;
desc[i].x = 'h20 + i;
end
end
for (genvar i = 0; i < 2; ++i) begin : g_2d_i
for (genvar j = 0; j < 3; ++j) begin : g_2d_j
initial a2d[i][j].x = 'h30 + 3 * i + j;
end
end
va_t w[4];
va_t s[2];
va_t w2d[2][3];
C c;
sub i_sub ();
initial begin
#1;
// Whole array, left element to left element
w = asc;
for (int i = 0; i < 4; ++i) `checkh(w[i].x, 'h10 + i);
w = desc;
for (int i = 0; i < 4; ++i) `checkh(w[i].x, 'h20 + 3 - i);
// Slices
s = asc[1:2];
`checkh(s[0].x, 'h11);
`checkh(s[1].x, 'h12);
s = desc[2:1];
`checkh(s[0].x, 'h22);
`checkh(s[1].x, 'h21);
// Multi dimensional, a2d[1] is the left row
w2d = a2d;
for (int i = 0; i < 2; ++i) begin
for (int j = 0; j < 3; ++j) `checkh(w2d[i][j].x, 'h30 + 3 * (1 - i) + j);
end
// Hierarchical reference to a whole array
s = i_sub.ifs;
`checkh(s[0].x, 'h40);
`checkh(s[1].x, 'h41);
`checkh(i_sub.ifs[0].x, 'h40);
`checkh(i_sub.ifs[1].x, 'h41);
// Class member
c = new;
c.vif = asc[2:3];
`checkh(c.vif[0].x, 'h12);
`checkh(c.vif[1].x, 'h13);
// Writes through the virtual interfaces reach the instances
c.vif[1].x = 'h99;
w2d[0][2].x = 'h98;
#1;
`checkh(asc[3].x, 'h99);
`checkh(a2d[1][2].x, 'h98);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule
+26 -23
View File
@@ -1,27 +1,30 @@
%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:23:7: Unexpected target of interface assignment ['IFACEREFDTYPE$[0:5]']
: ... note: In instance 'tb_top'
23 | a = f;
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:26:11: Array size mismatch in interface assignment
: ... note: In instance 'tb_top'
26 | c.vif = b;
| ^
%Error: t/t_interface_dearray_bad.v:31:20: Expecting expression to be constant, but variable isn't const: 'i'
%Error: t/t_interface_dearray_bad.v:27:21: Selection index out of range of interface array: 6 outside 5:0 (IEEE 1800-2023 23.6)
: ... note: In instance 'tb_top'
31 | d.vif[i] = a[i];
| ^
27 | sub s_oob_hi (.p(a[6]));
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:31:20: Non-constant index in RHS interface array selection
: ... note: In instance 'tb_top'
31 | d.vif[i] = a[i];
%Error: t/t_interface_dearray_bad.v:28:21: Selection index out of range of interface array: -1 outside 5:0 (IEEE 1800-2023 23.6)
: ... note: In instance 'tb_top'
28 | sub s_oob_lo (.p(a[-1]));
| ^
%Error: t/t_interface_dearray_bad.v:31:7: Illegal assignment to an interface instance (IEEE 1800-2023 10.4)
: ... note: In instance 'tb_top'
31 | a = f;
| ^
%Error: t/t_interface_dearray_bad.v:32:12: Illegal assignment to an interface instance (IEEE 1800-2023 10.4)
: ... note: In instance 'tb_top'
32 | a[0:1] = f[0:1];
| ^
%Error: t/t_interface_dearray_bad.v:33:10: Illegal assignment to an interface instance (IEEE 1800-2023 10.4)
: ... note: In instance 'tb_top'
33 | a[2] = f[2];
| ^
%Error: t/t_interface_dearray_bad.v:38:20: Expecting expression to be constant, but variable isn't const: 'i'
: ... note: In instance 'tb_top'
38 | d.vif[i] = a[i];
| ^
%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:35:14: Interface slices unsupported
: ... note: In instance 'tb_top'
35 | e.vif = b[0:5];
%Error: t/t_interface_dearray_bad.v:40:14: Slice selection index '[3:8]' outside data type's '[0:5]'
: ... note: In instance 'tb_top'
40 | d.vif = a[3:8];
| ^
%Error: Internal Error: t/t_interface_dearray_bad.v:23:9: ../V3Broken.cpp:#: Broken link in node (or something without maybePointedTo): 'm_varp && !m_varp->brokeExists()' @ ./V3Ast__gen_impl.h:#
: ... note: In instance 'tb_top'
23 | a = f;
| ^
... This fatal error may be caused by the earlier error(s); resolve those first.
%Error: Exiting due to
+14 -9
View File
@@ -7,6 +7,11 @@
interface A;
endinterface
module sub (
A p
);
endmodule
typedef virtual A a_t;
typedef a_t a_array_t[6];
@@ -15,24 +20,24 @@ class C;
endclass
module tb_top ();
A a[6] (), b[7] (), f[6] ();
C c, d, e;
a_array_t g;
A a[6] (), f[6] ();
C d;
// Bad: no such instances
sub s_oob_hi (.p(a[6]));
sub s_oob_lo (.p(a[-1]));
initial begin
a = f;
c = new();
c.vif = b;
a[0:1] = f[0:1];
a[2] = f[2];
d = new();
for (int i = 0; i < 6; ++i) begin
d.vif[i] = a[i];
end
e = new();
e.vif = b[0:5];
d.vif = a[3:8];
$write("*-* All Finished *-*\n");
$finish;
+20
View File
@@ -7,4 +7,24 @@
: ... note: In instance 't'
17 | baz baz6_inst (.foo(foo6));
| ^~~
%Error: t/t_interface_size_bad.v:23:22: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 2x4.
: ... note: In instance 't'
23 | baz2 baz2x4_inst (.foo(foo2x4));
| ^~~
%Error: t/t_interface_size_bad.v:24:22: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 3x3.
: ... note: In instance 't'
24 | baz2 baz3x3_inst (.foo(foo3x3));
| ^~~
%Error: t/t_interface_size_bad.v:25:21: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 6.
: ... note: In instance 't'
25 | baz2 baz6r_inst (.foo(foo6));
| ^~~
%Error: t/t_interface_size_bad.v:26:21: Illegal port connection 'foo', mismatch between port which is an interface array of size 5, and expression which is an interface array of size 2x5.
: ... note: In instance 't'
26 | baz baz2x5_inst (.foo(foo2x5));
| ^~~
%Error: t/t_interface_size_bad.v:29:27: Port connection 'foo' as part of a module instance array requires the connection's leading unpacked dimensions to match the instance array dimensions (IEEE 1800-2023 23.3.3.5)
: ... note: In instance 't'
29 | baz baz_arr_inst[1:0] (.foo(foo3x3));
| ^~~
%Error: Exiting due to
+17
View File
@@ -16,9 +16,26 @@ module t;
baz baz4_inst (.foo(foo4));
baz baz6_inst (.foo(foo6));
// Multi dimensional, each dimension must match
foo_intf foo2x4[1:0][3:0] ();
foo_intf foo3x3[2:0][2:0] ();
foo_intf foo2x5[1:0][4:0] ();
baz2 baz2x4_inst (.foo(foo2x4));
baz2 baz3x3_inst (.foo(foo3x3));
baz2 baz6r_inst (.foo(foo6));
baz baz2x5_inst (.foo(foo2x5));
// Instance array, leading dimension must match the instance array
baz baz_arr_inst[1:0] (.foo(foo3x3));
endmodule
module baz (
foo_intf foo[4:0]
);
endmodule
module baz2 (
foo_intf foo[1:0][2:0]
);
endmodule
+5 -1
View File
@@ -1,8 +1,12 @@
%Error: t/t_interface_virtual_bad.v:30:8: Illegal assignment to an interface instance (IEEE 1800-2023 10.4)
: ... note: In instance 't'
30 | p8 = v8;
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_virtual_bad.v:31:10: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'q8' is a different interface ('QBus').
: ... note: In instance 't'
31 | v8 = q8;
| ^~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_virtual_bad.v:35:10: Operator ASSIGN expected no interface modport on Assign RHS but got 'phy' modport.
: ... note: In instance 't'
35 | v8 = v8_phy;
@@ -0,0 +1,14 @@
%Error: t/t_interface_virtual_port_bad.v:26:15: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4)
: ... note: In instance 't'
26 | sub i_sub (.p(vs));
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_virtual_port_bad.v:27:23: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4)
: ... note: In instance 't'
27 | sub_arr i_sub_arr (.p(va));
| ^
%Error: t/t_interface_virtual_port_bad.v:28:19: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4)
: ... note: In instance 't'
28 | sub i_subs[2] (.p(va));
| ^
%Error: Exiting due to
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,29 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
interface a_if;
int x;
endinterface
module sub (
a_if p
);
endmodule
module sub_arr (
a_if p[2]
);
endmodule
module t;
virtual a_if vs;
virtual a_if va[2];
// Bad: interface ports need interface instances, not virtual interfaces
sub i_sub (.p(vs));
sub_arr i_sub_arr (.p(va));
sub i_subs[2] (.p(va));
endmodule
+72 -11
View File
@@ -14,13 +14,34 @@ interface a_if ();
string s;
endinterface
// TODO: V3Inst only handles a one-dimensional interface array element as a pin connection
module sub (
output string s
// , a_if i
);
initial s = $sformatf("%m");
// initial i.s = $sformatf("%m-iface");
endmodule
module isub (
output string s,
a_if i
);
initial s = $sformatf("%m");
initial i.s = $sformatf("%m-iface");
endmodule
// Writes each element of its interface array port
module awrite (
a_if p[3]
);
for (genvar k = 0; k < 3; ++k) begin : g
initial p[k].s = $sformatf("%m");
end
endmodule
// Passes a slice of its interface array port down
module amid (
a_if p[4]
);
awrite i_w (.p(p[1:3]));
endmodule
module pass (
@@ -51,7 +72,7 @@ module t;
a_if iface[3:1][1:0] ();
sub i_sub[3:1][1:0] (.s(str) /*, .i(iface)*/);
isub i_sub[3:1][1:0] (.s(str), .i(iface));
// Hierarchical references to elements, with genvar indices
for (genvar a = 1; a < 4; ++a) begin : g_a
@@ -103,6 +124,26 @@ module t;
logic [3:0] uo3[1:0];
usub i_u[1:0] (.a(ux), .o0(uo0), .o3(uo3));
// Elements of a module instance array with an interface array port, connected to a
// two-dimensional interface array, in opposite directions: connect left to left
a_if i2d[2:1][2:0] ();
awrite i_aw[1:2] (.p(i2d));
// One row of a two-dimensional interface array connected to an interface array port
a_if irow[1:0][0:2] ();
awrite i_row (.p(irow[1]));
// A slice of one row of a two-dimensional interface array connected to an interface array
// port, and an element of one connected to an interface port
a_if irs[1:0][0:3] ();
awrite i_rsl (.p(irs[1][1:3]));
string elstr;
isub i_el (.s(elstr), .i(irs[0][2]));
// A slice of an interface array port passed down
a_if i4[4] ();
amid i_mid (.p(i4));
// Ref port connected to an element of an unpacked array
string rstr[1:0];
rsub i_ref[1:0] (.r(rstr));
@@ -142,6 +183,27 @@ module t;
`checkh(uo0[0], 4'h9);
`checkh(uo3[1], 4'h6);
`checkh(uo3[0], 4'h6);
// i_aw[1] is the left element, i2d[2]; port element p[0] is the left element, [2]
`checks(i2d[2][2].s, "t.i_aw[1].g[0]");
`checks(i2d[2][1].s, "t.i_aw[1].g[1]");
`checks(i2d[2][0].s, "t.i_aw[1].g[2]");
`checks(i2d[1][2].s, "t.i_aw[2].g[0]");
`checks(i2d[1][1].s, "t.i_aw[2].g[1]");
`checks(i2d[1][0].s, "t.i_aw[2].g[2]");
`checks(irow[1][0].s, "t.i_row.g[0]");
`checks(irow[1][1].s, "t.i_row.g[1]");
`checks(irow[1][2].s, "t.i_row.g[2]");
`checks(irow[0][0].s, "");
`checks(irs[1][0].s, "");
`checks(irs[1][1].s, "t.i_rsl.g[0]");
`checks(irs[1][2].s, "t.i_rsl.g[1]");
`checks(irs[1][3].s, "t.i_rsl.g[2]");
`checks(irs[0][2].s, "t.i_el-iface");
`checks(irs[0][1].s, "");
`checks(i4[0].s, "");
`checks(i4[1].s, "t.i_mid.i_w.g[0]");
`checks(i4[2].s, "t.i_mid.i_w.g[1]");
`checks(i4[3].s, "t.i_mid.i_w.g[2]");
`checks(rstr[1], "t.i_ref[1]");
`checks(rstr[0], "t.i_ref[0]");
`checks(str[1][0], "t.i_sub[1][0]");
@@ -150,13 +212,12 @@ module t;
`checks(str[2][1], "t.i_sub[2][1]");
`checks(str[3][0], "t.i_sub[3][0]");
`checks(str[3][1], "t.i_sub[3][1]");
// TODO: enable with the interface port of 'sub'
// `checks(iface[1][0].s, "t.i_sub[1][0]-iface");
// `checks(iface[1][1].s, "t.i_sub[1][1]-iface");
// `checks(iface[2][0].s, "t.i_sub[2][0]-iface");
// `checks(iface[2][1].s, "t.i_sub[2][1]-iface");
// `checks(iface[3][0].s, "t.i_sub[3][0]-iface");
// `checks(iface[3][1].s, "t.i_sub[3][1]-iface");
`checks(iface[1][0].s, "t.i_sub[1][0]-iface");
`checks(iface[1][1].s, "t.i_sub[1][1]-iface");
`checks(iface[2][0].s, "t.i_sub[2][0]-iface");
`checks(iface[2][1].s, "t.i_sub[2][1]-iface");
`checks(iface[3][0].s, "t.i_sub[3][0]-iface");
`checks(iface[3][1].s, "t.i_sub[3][1]-iface");
$write("*-* All Finished *-*\n");
$finish;
end