This commit is contained in:
em2machine 2026-04-21 20:31:51 +00:00 committed by GitHub
commit 96993c2f6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1236 additions and 139 deletions

View File

@ -656,7 +656,8 @@ class AstCell final : public AstNode {
// A instantiation cell or interface call (don't know which until link)
// @astgen op1 := pinsp : List[AstPin] // List of port assignments
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
// @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances
// @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains
// via nextp()
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing
//
// @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
@ -680,7 +681,7 @@ public:
, m_trace{true} {
addPinsp(pinsp);
addParamsp(paramsp);
this->rangep(rangep);
addRangep(rangep);
}
ASTGEN_MEMBERS_AstCell;
// No cloneRelink, we presume cloneee's want the same module linkages

View File

@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor {
// Find all cells with arrays, and convert to non-arrayed
private:
// STATE
// Range for arrayed instantiations, nullptr for normal instantiations
const AstRange* m_cellRangep = nullptr;
int m_instSelNum = 0; // Current instantiation count 0..N-1
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
// VISITORS
@ -233,52 +232,72 @@ private:
m_deModVars.main(nodep->modp());
//
if (nodep->rangep()) {
m_cellRangep = nodep->rangep();
// 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);
// cppcheck-suppress constVariablePointer
AstNodeDType* const ifaceVarDtp
= ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr;
const bool isIface
= ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType)
&& VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
&& !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
->isVirtual();
// 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();
// Make all of the required clones
for (int i = 0; i < m_cellRangep->elementsConst(); i++) {
m_instSelNum
= m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i;
const int instNum = m_cellRangep->loConst() + i;
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);
// Remove ranging and fix name
newp->rangep()->unlinkFrBack()->deleteTree();
// Somewhat illogically, we need to rename the original name of the cell too.
// as that is the name users expect for dotting
// The spec says we add [x], but that won't work in C...
newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__");
while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
newp->name(newp->name() + suffix);
newp->origName(newp->origName() + suffix);
UINFO(8, " CELL loop " << newp);
// If this AstCell is actually an interface instantiation, also clone the IfaceRef
// within the same parent module as the cell
// Interface instantiation: also clone the IfaceRef in the parent module.
if (isIface) {
AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType);
AstIfaceRefDType* const origIfaceRefp
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType);
origIfaceRefp->cellp(nullptr);
AstVar* const varNewp = ifaceVarp->cloneTree(false);
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
arrdtype->addNextHere(ifaceRefp);
innermostArrp->addNextHere(ifaceRefp);
ifaceRefp->cellp(newp);
ifaceRefp->cellName(newp->name());
varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum)
+ "__KET__");
varNewp->name(varNewp->name() + suffix);
varNewp->origName(varNewp->origName() + suffix);
varNewp->dtypep(ifaceRefp);
newp->addNextHere(varNewp);
if (debug() == 9) {
@ -389,6 +408,109 @@ private:
}
} 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];
}
const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef);
if (!varrefp) {
nodep->exprp()->v3error("Unexpected connection to arrayed port");
return;
}
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;
}
}
if (exprArrs.size() != static_cast<size_t>(ndim)) {
nodep->exprp()->v3error(
"Multi-dim iface pin expression rank does not match port");
return;
}
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);
if (!prevp) {
prevp = varNewp;
} else {
prevp->addNextHere(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);
if (!prevPinp) {
prevPinp = newp;
} else {
prevPinp->addNextHere(newp);
}
}
if (prevp) {
pinVarp->replaceWith(prevp);
pushDeletep(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;
@ -472,28 +594,50 @@ private:
}
}
void visit(AstArraySel* nodep) override {
if (const AstUnpackArrayDType* const arrp
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
V3Const::constifyParamsEdit(nodep->bitp());
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
// 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) {
nodep->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
asp->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
return;
}
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo());
const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
UASSERT_OBJ(varrefp, nodep, "No interface varref under array");
AstVarXRef* const newp = new AstVarXRef{
nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "",
VAccess::READ};
newp->dtypep(arrp->subDTypep());
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
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__";
}
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)) {

View File

@ -776,12 +776,19 @@ 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
AstNodeArrayDType* const arrp
= new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep,
nodep->rangep()->cloneTree(true)};
// 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)) {
rangesp.push_back(rp);
}
AstNodeDType* dtp = idtypep;
for (auto it = rangesp.rbegin(); it != rangesp.rend(); ++it) {
dtp = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, dtp,
(*it)->cloneTree(false)};
}
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, arrp};
VFlagChildDType{}, dtp};
} else {
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, idtypep};

View File

@ -524,17 +524,19 @@ public:
void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE
m_ifaceVarSyms.push_back(symp);
}
// Iface for a raw or arrayed iface
// Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays.
static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) {
AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType);
if (!ifacerefp) {
while (nodep) {
if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp;
if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else {
return nullptr;
}
}
return ifacerefp;
return nullptr;
}
// Given a pin expression, resolve it to a live AstIface* (or nullptr).
// Handles both simple VarRef and dotted VarXRef pin connections.
@ -770,10 +772,16 @@ public:
if (forPrearray()) {
// GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled,
// but presently should be just "foo".
// Likewise cell foo__[array] before we've expanded arrays is just foo
if ((pos = ident.rfind("__BRA__")) != string::npos) {
altIdent = ident.substr(0, pos);
// Likewise cell foo__[array] before we've expanded arrays is just foo.
// Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them
// all.
altIdent = ident;
while (VString::endsWith(altIdent, "__KET__")) {
const auto braPos = altIdent.rfind("__BRA__");
if (braPos == string::npos) break;
altIdent = altIdent.substr(0, braPos);
}
if (altIdent == ident) altIdent.clear();
}
UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname
<< " at se" << lookupSymp);
@ -5316,10 +5324,18 @@ class LinkDotResolveVisitor final : public VNVisitor {
symIterateNull(nodep->attrp(), m_curSymp);
if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) {
AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack();
AstCellArrayRef* const newp
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) {
// Multi-dim iface array access: append this select to the existing chain
fromArrRefp->addSelp(exprp);
AstCellArrayRef* const movedp = fromArrRefp->unlinkFrBack();
nodep->replaceWith(movedp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
AstCellArrayRef* const newp
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
void visit(AstNodePreSel* nodep) override {

View File

@ -475,6 +475,12 @@ class ParamProcessor final {
}
return nullptr;
}
// Peel all unpacked-array layers to reach the innermost subDTypep.
// Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType.
static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) {
while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp;
return nodep;
}
static bool isString(AstNodeDType* nodep) {
if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType))
return basicp->isString();
@ -1613,38 +1619,32 @@ class ParamProcessor final {
const AstVar* const modvarp = pinp->modVarp();
if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue;
if (modvarp->isIfaceRef()) {
AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType);
if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) {
portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType);
}
AstIfaceRefDType* pinIrefp = nullptr;
// arraySubDTypeDeepp returns input unchanged if not an array.
AstIfaceRefDType* const portIrefp
= VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType);
const AstNode* const exprp = pinp->exprp();
const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef))
? VN_AS(exprp, NodeVarRef)->varp()
: nullptr;
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType);
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
&& VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) {
pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType);
} else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef)
&& VN_CAST(exprp->op1p(), VarRef)->varp()
&& VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()
&& arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep())
&& VN_CAST(
arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType)) {
pinIrefp
= VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType);
} else if (VN_IS(exprp, CellArrayRef)) {
// Interface array element selection (e.g., l1(l2.l1[0]) for nested iface
// array) The CellArrayRef is not yet fully linked to an interface type. Skip
// interface cleanup for this pin - V3LinkDot will resolve this later. Just
// continue to the next pin without error.
if (VN_IS(exprp, CellArrayRef)) {
// CellArrayRef not yet linked; V3LinkDot resolves this pin later.
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp);
continue;
}
AstIfaceRefDType* pinIrefp = nullptr;
// Pin is a VarRef to a var of (possibly arrayed) iface type.
if (const AstNodeVarRef* const vrp = VN_CAST(exprp, NodeVarRef)) {
if (vrp->varp()) {
pinIrefp
= VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType);
}
}
// Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array).
if (!pinIrefp && exprp) {
if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) {
if (vrp->varp()) {
pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()),
IfaceRefDType);
}
}
}
UINFO(9, " portIfaceRef " << portIrefp);
@ -2837,33 +2837,46 @@ class ParamVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstCellArrayRef* nodep) override {
V3Const::constifyParamsEdit(nodep->selp());
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) {
const string index = AstNode::encodeNumber(constp->toSInt());
// For nested interface array ports, the node name may have a __Viftop suffix
// that doesn't exist in the original unlinked text. Try without the suffix.
const string viftopSuffix = "__Viftop";
const string baseName
= VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
: nodep->name();
const string replacestr = baseName + "__BRA__??__KET__";
const size_t pos = m_unlinkedTxt.find(replacestr);
// For interface port array element selections (e.g., l1(l2.l1[0])),
// the AstCellArrayRef may be visited outside of an AstUnlinkedRef context.
// In such cases, m_unlinkedTxt won't contain the expected pattern.
// Simply skip the replacement - the cell array ref will be resolved later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep);
// Multi-dim iface arrays chain multiple selps via nextp(); constify each in place.
for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) {
nxt = s->nextp(); // cache before constifyParamsEdit replaces s
V3Const::constifyParamsEdit(s);
}
std::vector<int> indices;
for (AstNode* s = nodep->selp(); s; s = s->nextp()) {
const AstConst* const constp = VN_CAST(s, Const);
if (!constp) {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< s->prettyNameQ());
return;
}
m_unlinkedTxt.replace(pos, replacestr.length(),
baseName + "__BRA__" + index + "__KET__");
} else {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< nodep->selp()->prettyNameQ());
indices.push_back(constp->toSInt());
}
// Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt.
const string viftopSuffix = "__Viftop";
const string baseName
= VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
: nodep->name();
const string placeholder = "__BRA__??__KET__";
size_t pos = m_unlinkedTxt.find(baseName + placeholder);
// An AstCellArrayRef for an iface-array pin expr (e.g. l1(l2.l1[0])) is visited
// outside AstUnlinkedRef, so m_unlinkedTxt has no placeholder; V3LinkDot resolves later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep);
return;
}
pos += baseName.length();
for (int idx : indices) {
const string replacement = "__BRA__" + AstNode::encodeNumber(idx) + "__KET__";
if (m_unlinkedTxt.compare(pos, placeholder.length(), placeholder) != 0) {
nodep->v3fatalSrc( // LCOV_EXCL_LINE
"Expected placeholder at position in multi-dim iface dotted reference");
return;
}
m_unlinkedTxt.replace(pos, placeholder.length(), replacement);
pos += replacement.length();
}
}
// Generate Statements

View File

@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n
return assignp;
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list
AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list; preserves multi-dim chain via nextp().
for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) {
nextp = VN_AS(nodep->nextp(), NodeRange);
if (!VN_IS(nodep, Range)) {
@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
if (nrangep && nrangep->nextp()) {
// Not supported by at least 2 of big 3
nrangep->nextp()->v3warn(E_UNSUPPORTED,
"Unsupported: Multidimensional instances/interfaces.");
nrangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return VN_CAST(nrangep, Range);
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
AstRange* const rangep = scrubRangeMulti(nrangep);
if (rangep && rangep->nextp()) {
// Gate primitives only support a single dimension
rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances.");
rangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return rangep;
}
AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED {
// SEL(PARSELVALUE, ...) -> SEL(fromp, ...)

View File

@ -71,6 +71,7 @@ public:
return v3Global.opt.trace() && m_tracingParse && fl->tracingOn();
}
AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED;
AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED;
AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED;
AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep,
bool isPacked) VL_MT_DISABLED;
@ -90,7 +91,7 @@ public:
singletonp()->m_instModule,
pinlistp,
(singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr),
singletonp()->scrubRange(rangelistp)};
singletonp()->scrubRangeMulti(rangelistp)};
nodep->trace(singletonp()->allTracingOn(fileline));
return nodep;
}

View File

@ -93,6 +93,7 @@ for s in [
'Invalid reference: Process might outlive variable',
'Modport item is not a function/task:',
'Modport item is not a variable:',
'Multi-dim iface pin expression rank does not match port',
'Modport not referenced as <interface>.',
'Modport not referenced from underneath an interface:',
'Need $SYSTEMC_INCLUDE in environment or when Verilator configured,',

View File

@ -0,0 +1,5 @@
%Error-UNSUPPORTED: t/t_gate_array_multidim_bad.v:12:14: Unsupported: Multidimensional gate instances.
12 | and g [1:0][1:0] (y, a, b);
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

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

View File

@ -0,0 +1,13 @@
// 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
// Verify multi-dim gate primitive arrays are rejected.
module t;
wire a, b;
wire [1:0][1:0] y;
and g [1:0][1:0] (y, a, b);
endmodule

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

View File

@ -0,0 +1,54 @@
// 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
// Multi-dimensional interface instance arrays: minimal 2D reproducer.
// Exercises declaration, dotted access, multi-dim port connection and
// VCD-relevant naming.
interface simple_if;
logic [7:0] data;
endinterface
module t;
localparam int A = 2;
localparam int B = 3;
simple_if bus [A-1:0][B-1:0] ();
genvar gi, gj;
generate
for (gi = 0; gi < A; gi++) begin : g_a
for (gj = 0; gj < B; gj++) begin : g_b
initial bus[gi][gj].data = 8'(gi * B + gj + 1);
end
end
endgenerate
// Runtime check via a chk array populated by the same genvar generate block.
logic [7:0] chk [A-1:0][B-1:0];
generate
for (gi = 0; gi < A; gi++) begin : g_a_chk
for (gj = 0; gj < B; gj++) begin : g_b_chk
always_comb chk[gi][gj] = bus[gi][gj].data;
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < A; i++) begin
for (int j = 0; j < B; j++) begin
if (chk[i][j] !== 8'(i * B + j + 1)) begin
$write("%%Error: bus[%0d][%0d].data=%0d expected %0d\n",
i, j, chk[i][j], i * B + j + 1);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,58 @@
// 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
// 3D interface instance arrays. Ensures arbitrary rank, not just 2D.
interface simple_if;
logic [15:0] data;
endinterface
module t;
localparam int A = 2;
localparam int B = 2;
localparam int C = 3;
simple_if bus [A-1:0][B-1:0][C-1:0] ();
genvar gi, gj, gk;
generate
for (gi = 0; gi < A; gi++) begin : g_a
for (gj = 0; gj < B; gj++) begin : g_b
for (gk = 0; gk < C; gk++) begin : g_c
initial bus[gi][gj][gk].data = 16'(gi * B * C + gj * C + gk + 1);
end
end
end
endgenerate
logic [15:0] chk [A-1:0][B-1:0][C-1:0];
generate
for (gi = 0; gi < A; gi++) begin : g_a_chk
for (gj = 0; gj < B; gj++) begin : g_b_chk
for (gk = 0; gk < C; gk++) begin : g_c_chk
always_comb chk[gi][gj][gk] = bus[gi][gj][gk].data;
end
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < A; i++) begin
for (int j = 0; j < B; j++) begin
for (int k = 0; k < C; k++) begin
if (chk[i][j][k] !== 16'(i * B * C + j * C + k + 1)) begin
$write("%%Error: bus[%0d][%0d][%0d].data=%0d expected %0d\n",
i, j, k, chk[i][j][k], i * B * C + j * C + k + 1);
$stop;
end
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,59 @@
// 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
// 3D iface-array port passed to a submodule. t_iface_array_multidim_3d
// covers 3D local declaration; this covers 3D through a port.
interface simple_if;
logic [15:0] data;
endinterface
module sink (simple_if b [1:0][1:0][2:0]);
logic [15:0] chk [1:0][1:0][2:0];
genvar gi, gj, gk;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 2; gj++) begin : g_b
for (gk = 0; gk < 3; gk++) begin : g_c
always_comb chk[gi][gj][gk] = b[gi][gj][gk].data;
end
end
end
endgenerate
endmodule
module t;
simple_if bus [1:0][1:0][2:0] ();
sink inst (.b(bus));
genvar gi, gj, gk;
generate
for (gi = 0; gi < 2; gi++) begin : g_drive_a
for (gj = 0; gj < 2; gj++) begin : g_drive_b
for (gk = 0; gk < 3; gk++) begin : g_drive_c
initial bus[gi][gj][gk].data = 16'(gi * 6 + gj * 3 + gk + 1);
end
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 2; j++) begin
for (int k = 0; k < 3; k++) begin
if (inst.chk[i][j][k] !== 16'(i * 6 + j * 3 + k + 1)) begin
$write("%%Error: chk[%0d][%0d][%0d]=%0d expected %0d\n",
i, j, k, inst.chk[i][j][k], i * 6 + j * 3 + k + 1);
$stop;
end
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,58 @@
// 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
// Three-level hierarchy passing a multi-dim iface array by port at each
// boundary. Top reads leaf's chk array via dotted cross-hier reference,
// which also exercises the multi-dim dotted-access resolver.
interface simple_if;
logic [7:0] data;
endinterface
module leaf (simple_if b [1:0][2:0]);
logic [7:0] chk [1:0][2:0];
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 3; gj++) begin : g_b
always_comb chk[gi][gj] = b[gi][gj].data;
end
end
endgenerate
endmodule
module mid (simple_if b [1:0][2:0]);
leaf leaf_inst (.b(b));
endmodule
module t;
simple_if bus [1:0][2:0] ();
mid mid_inst (.b(bus));
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_drive_a
for (gj = 0; gj < 3; gj++) begin : g_drive_b
initial bus[gi][gj].data = 8'(gi * 3 + gj + 7);
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 3; j++) begin
if (mid_inst.leaf_inst.chk[i][j] !== 8'(i * 3 + j + 7)) begin
$write("%%Error: leaf.chk[%0d][%0d]=%0d expected %0d\n",
i, j, mid_inst.leaf_inst.chk[i][j], i * 3 + j + 7);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,59 @@
// 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
// Multi-dim iface array ports typed with a modport (both source and sink).
// Mirrors 1D modport-port coverage in t_interface_array_loop for the
// multi-dim pin-fanout path.
interface simple_if;
logic [7:0] data;
modport source(output data);
modport sink(input data);
endinterface
module src (simple_if.source b [1:0][2:0]);
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 3; gj++) begin : g_b
initial b[gi][gj].data = 8'(gi * 3 + gj + 20);
end
end
endgenerate
endmodule
module snk (simple_if.sink b [1:0][2:0]);
logic [7:0] chk [1:0][2:0];
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 3; gj++) begin : g_b
always_comb chk[gi][gj] = b[gi][gj].data;
end
end
endgenerate
endmodule
module t;
simple_if bus [1:0][2:0] ();
src src_inst (.b(bus));
snk snk_inst (.b(bus));
initial begin
#1;
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 3; j++) begin
if (snk_inst.chk[i][j] !== 8'(i * 3 + j + 20)) begin
$write("%%Error: chk[%0d][%0d]=%0d expected %0d\n",
i, j, snk_inst.chk[i][j], i * 3 + j + 20);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,68 @@
// 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
// Multi-dim iface array where outer iface contains an inner iface.
// outer_if oarr[A][B](), outer_if holds a single inner_if instance.
// Access via oarr[i][j].inner.data.
interface inner_if;
logic [7:0] data;
endinterface
interface outer_if;
inner_if inner();
logic [7:0] tag;
endinterface
module t;
localparam int A = 2;
localparam int B = 2;
outer_if oarr [A-1:0][B-1:0] ();
genvar gi, gj;
generate
for (gi = 0; gi < A; gi++) begin : g_a
for (gj = 0; gj < B; gj++) begin : g_b
initial begin
oarr[gi][gj].tag = 8'(gi * 16 + gj);
oarr[gi][gj].inner.data = 8'(gi * B + gj + 100);
end
end
end
endgenerate
logic [7:0] chk_tag [A-1:0][B-1:0];
logic [7:0] chk_inner [A-1:0][B-1:0];
generate
for (gi = 0; gi < A; gi++) begin : g_a_chk
for (gj = 0; gj < B; gj++) begin : g_b_chk
always_comb chk_tag[gi][gj] = oarr[gi][gj].tag;
always_comb chk_inner[gi][gj] = oarr[gi][gj].inner.data;
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < A; i++) begin
for (int j = 0; j < B; j++) begin
if (chk_tag[i][j] !== 8'(i * 16 + j)) begin
$write("%%Error: oarr[%0d][%0d].tag=%0d expected %0d\n",
i, j, chk_tag[i][j], i * 16 + j);
$stop;
end
if (chk_inner[i][j] !== 8'(i * B + j + 100)) begin
$write("%%Error: oarr[%0d][%0d].inner.data=%0d expected %0d\n",
i, j, chk_inner[i][j], i * B + j + 100);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,69 @@
// 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
// outer_if contains inner_if; an array of outer_if is passed as a port to
// a submodule which reaches through to inner_if's signal. Exercises
// hierarchical iface reference across the fanned-out multi-dim port.
interface inner_if;
logic [7:0] data;
endinterface
interface outer_if;
inner_if inner();
logic [7:0] tag;
endinterface
module sink (outer_if b [1:0][1:0]);
logic [7:0] chk_tag [1:0][1:0];
logic [7:0] chk_inner [1:0][1:0];
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 2; gj++) begin : g_b
always_comb chk_tag[gi][gj] = b[gi][gj].tag;
always_comb chk_inner[gi][gj] = b[gi][gj].inner.data;
end
end
endgenerate
endmodule
module t;
outer_if oarr [1:0][1:0] ();
sink inst (.b(oarr));
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_drive_a
for (gj = 0; gj < 2; gj++) begin : g_drive_b
initial begin
oarr[gi][gj].tag = 8'(gi * 16 + gj);
oarr[gi][gj].inner.data = 8'(gi * 2 + gj + 100);
end
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 2; j++) begin
if (inst.chk_tag[i][j] !== 8'(i * 16 + j)) begin
$write("%%Error: chk_tag[%0d][%0d]=%0d expected %0d\n",
i, j, inst.chk_tag[i][j], i * 16 + j);
$stop;
end
if (inst.chk_inner[i][j] !== 8'(i * 2 + j + 100)) begin
$write("%%Error: chk_inner[%0d][%0d]=%0d expected %0d\n",
i, j, inst.chk_inner[i][j], i * 2 + j + 100);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,55 @@
// 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
// Multi-dim iface array passed as a port to a submodule (the point of
// multi-dim iface arrays). Exercises the multi-dim pin-fanout branch in
// V3Inst::visit(AstPin) and the multi-dim branch in V3Inst::visit(AstVar)
// on the sink's port var.
interface simple_if;
logic [7:0] data;
endinterface
module sink (simple_if b [1:0][2:0]);
logic [7:0] chk [1:0][2:0];
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 3; gj++) begin : g_b
always_comb chk[gi][gj] = b[gi][gj].data;
end
end
endgenerate
endmodule
module t;
simple_if bus [1:0][2:0] ();
sink inst (.b(bus));
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_drive_a
for (gj = 0; gj < 3; gj++) begin : g_drive_b
initial bus[gi][gj].data = 8'(gi * 3 + gj + 1);
end
end
endgenerate
initial begin
#1;
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 3; j++) begin
if (inst.chk[i][j] !== 8'(i * 3 + j + 1)) begin
$write("%%Error: inst.chk[%0d][%0d]=%0d expected %0d\n",
i, j, inst.chk[i][j], i * 3 + j + 1);
$stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,40 @@
// 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
// Multi-dim iface array port, sink WRITES into the iface signals, top reads.
// Complements t_iface_array_multidim_port (which has sink reading).
interface simple_if;
logic [7:0] data;
endinterface
module src (simple_if b [1:0][2:0]);
genvar gi, gj;
generate
for (gi = 0; gi < 2; gi++) begin : g_a
for (gj = 0; gj < 3; gj++) begin : g_b
initial b[gi][gj].data = 8'(gi * 3 + gj + 50);
end
end
endgenerate
endmodule
module t;
simple_if bus [1:0][2:0] ();
src inst (.b(bus));
initial begin
#1;
if (bus[0][0].data !== 8'd50) begin $write("%%Error: bus[0][0]=%0d\n", bus[0][0].data); $stop; end
if (bus[0][1].data !== 8'd51) begin $write("%%Error: bus[0][1]=%0d\n", bus[0][1].data); $stop; end
if (bus[0][2].data !== 8'd52) begin $write("%%Error: bus[0][2]=%0d\n", bus[0][2].data); $stop; end
if (bus[1][0].data !== 8'd53) begin $write("%%Error: bus[1][0]=%0d\n", bus[1][0].data); $stop; end
if (bus[1][1].data !== 8'd54) begin $write("%%Error: bus[1][1]=%0d\n", bus[1][1].data); $stop; end
if (bus[1][2].data !== 8'd55) begin $write("%%Error: bus[1][2]=%0d\n", bus[1][2].data); $stop; end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
#!/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')
# -Wno-ASCRANGE: this test intentionally declares ascending iface array ranges
# ([0:N-1]) to exercise the ascending() branch in V3Inst.
test.compile(verilator_flags2=["--binary", "-Wno-ASCRANGE"])
test.execute()
test.passes()

View File

@ -0,0 +1,81 @@
// 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
// Multi-dim iface arrays with ascending (left<right) ranges and negative
// indices. The descending/zero-based case is covered by t_iface_array_multidim*;
// this test exercises the ascending() branch in V3Inst and negative lo() in
// name mangling.
interface simple_if;
logic [7:0] data;
endinterface
module t;
// Both dims ascending, zero-based.
simple_if asc [0:1][0:2] ();
// Outer descending, inner ascending (mixed endianness).
simple_if mix [1:0][0:2] ();
// Negative indices: outer descending (1..-1), inner ascending (-2..0).
simple_if neg [1:-1][-2:0] ();
initial begin
asc[0][0].data = 8'd10;
asc[0][1].data = 8'd11;
asc[0][2].data = 8'd12;
asc[1][0].data = 8'd13;
asc[1][1].data = 8'd14;
asc[1][2].data = 8'd15;
mix[0][0].data = 8'd20;
mix[0][1].data = 8'd21;
mix[0][2].data = 8'd22;
mix[1][0].data = 8'd23;
mix[1][1].data = 8'd24;
mix[1][2].data = 8'd25;
neg[-1][-2].data = 8'd50;
neg[-1][-1].data = 8'd51;
neg[-1][0].data = 8'd52;
neg[0][-2].data = 8'd53;
neg[0][-1].data = 8'd54;
neg[0][0].data = 8'd55;
neg[1][-2].data = 8'd56;
neg[1][-1].data = 8'd57;
neg[1][0].data = 8'd58;
end
initial begin
#1;
if (asc[0][0].data !== 8'd10) begin $write("%%Error: asc[0][0]=%0d\n", asc[0][0].data); $stop; end
if (asc[0][1].data !== 8'd11) begin $write("%%Error: asc[0][1]=%0d\n", asc[0][1].data); $stop; end
if (asc[0][2].data !== 8'd12) begin $write("%%Error: asc[0][2]=%0d\n", asc[0][2].data); $stop; end
if (asc[1][0].data !== 8'd13) begin $write("%%Error: asc[1][0]=%0d\n", asc[1][0].data); $stop; end
if (asc[1][1].data !== 8'd14) begin $write("%%Error: asc[1][1]=%0d\n", asc[1][1].data); $stop; end
if (asc[1][2].data !== 8'd15) begin $write("%%Error: asc[1][2]=%0d\n", asc[1][2].data); $stop; end
if (mix[0][0].data !== 8'd20) begin $write("%%Error: mix[0][0]=%0d\n", mix[0][0].data); $stop; end
if (mix[0][1].data !== 8'd21) begin $write("%%Error: mix[0][1]=%0d\n", mix[0][1].data); $stop; end
if (mix[0][2].data !== 8'd22) begin $write("%%Error: mix[0][2]=%0d\n", mix[0][2].data); $stop; end
if (mix[1][0].data !== 8'd23) begin $write("%%Error: mix[1][0]=%0d\n", mix[1][0].data); $stop; end
if (mix[1][1].data !== 8'd24) begin $write("%%Error: mix[1][1]=%0d\n", mix[1][1].data); $stop; end
if (mix[1][2].data !== 8'd25) begin $write("%%Error: mix[1][2]=%0d\n", mix[1][2].data); $stop; end
if (neg[-1][-2].data !== 8'd50) begin $write("%%Error: neg[-1][-2]=%0d\n", neg[-1][-2].data); $stop; end
if (neg[-1][-1].data !== 8'd51) begin $write("%%Error: neg[-1][-1]=%0d\n", neg[-1][-1].data); $stop; end
if (neg[-1][0].data !== 8'd52) begin $write("%%Error: neg[-1][0]=%0d\n", neg[-1][0].data); $stop; end
if (neg[0][-2].data !== 8'd53) begin $write("%%Error: neg[0][-2]=%0d\n", neg[0][-2].data); $stop; end
if (neg[0][-1].data !== 8'd54) begin $write("%%Error: neg[0][-1]=%0d\n", neg[0][-1].data); $stop; end
if (neg[0][0].data !== 8'd55) begin $write("%%Error: neg[0][0]=%0d\n", neg[0][0].data); $stop; end
if (neg[1][-2].data !== 8'd56) begin $write("%%Error: neg[1][-2]=%0d\n", neg[1][-2].data); $stop; end
if (neg[1][-1].data !== 8'd57) begin $write("%%Error: neg[1][-1]=%0d\n", neg[1][-1].data); $stop; end
if (neg[1][0].data !== 8'd58) begin $write("%%Error: neg[1][0]=%0d\n", neg[1][0].data); $stop; end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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

View File

@ -0,0 +1,64 @@
// 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
// Cross-hierarchy dotted refs through a multi-dim iface array of a
// parameterized interface. Exercises IfaceCapture plus the multi-dim
// dotted-access resolver together.
interface bus_if #(parameter int W = 8);
logic [W-1:0] data;
endinterface
// Wrapper holding the multi-dim iface array; parent reads its cells via dots.
module holder;
localparam int A = 2;
localparam int B = 3;
bus_if #(.W(8)) bus [A-1:0][B-1:0] ();
genvar gi, gj;
generate
for (gi = 0; gi < A; gi++) begin : g_a
for (gj = 0; gj < B; gj++) begin : g_b
initial bus[gi][gj].data = 8'(gi * B + gj + 5);
end
end
endgenerate
endmodule
module t;
holder h();
initial begin
#1;
if (h.bus[0][0].data !== 8'd5) begin
$write("%%Error: h.bus[0][0].data=%0d expected 5\n", h.bus[0][0].data);
$stop;
end
if (h.bus[0][1].data !== 8'd6) begin
$write("%%Error: h.bus[0][1].data=%0d expected 6\n", h.bus[0][1].data);
$stop;
end
if (h.bus[0][2].data !== 8'd7) begin
$write("%%Error: h.bus[0][2].data=%0d expected 7\n", h.bus[0][2].data);
$stop;
end
if (h.bus[1][0].data !== 8'd8) begin
$write("%%Error: h.bus[1][0].data=%0d expected 8\n", h.bus[1][0].data);
$stop;
end
if (h.bus[1][1].data !== 8'd9) begin
$write("%%Error: h.bus[1][1].data=%0d expected 9\n", h.bus[1][1].data);
$stop;
end
if (h.bus[1][2].data !== 8'd10) begin
$write("%%Error: h.bus[1][2].data=%0d expected 10\n", h.bus[1][2].data);
$stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,8 +1,20 @@
%Error-UNSUPPORTED: t/t_mod_interface_array3.v:26:18: Unsupported: Multidimensional instances/interfaces.
26 | a_if iface[2:0][1:0] ();
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_mod_interface_array3.v:28:17: Unsupported: Multidimensional instances/interfaces.
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[2][1]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[2][0]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[1][1]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[1][0]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[0][1]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
%Error: t/t_mod_interface_array3.v:28:25: VARREF 't.__Vcellout__i_sub[0][0]__s' is not an unpacked array, but is in an unpacked array context
28 | sub i_sub[2:0][1:0] (.s(str));
| ^
%Error: Exiting due to