mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Support nested array element member access in constraint (#8243)
This commit is contained in:
+176
-38
@@ -786,6 +786,91 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
|
||||
AstNodeExpr* m_conditionp = nullptr; // Condition under which current expression is defined
|
||||
// (nullptr == always defined)
|
||||
AstNode* m_firstExpressionInsideIndexp = nullptr;
|
||||
|
||||
class NestedAccessPath final {
|
||||
AstMemberSel* m_topNestedArrayMemberSelp
|
||||
= nullptr; // Top node of nested array/member access
|
||||
AstSFormatF* m_nestedNameFormatTopp = nullptr; // Top node of variable's name format
|
||||
AstSFormatF* m_nestedNameFormatp = nullptr; // Last node of variable's name format
|
||||
AstNode** m_firstExpressionInsideIndexPointerp
|
||||
= nullptr; // First expression with no name, used for "Multiple expressions inside
|
||||
// indices of complex expression in constraint" error
|
||||
|
||||
std::string m_smtName; // string name for shouldWriteVar checking
|
||||
|
||||
public:
|
||||
void addVarNamePart(AstSFormatF* const nodep, std::string&& name) {
|
||||
if (!m_nestedNameFormatp) {
|
||||
m_nestedNameFormatTopp = nodep;
|
||||
m_nestedNameFormatp = m_nestedNameFormatTopp;
|
||||
} else {
|
||||
m_nestedNameFormatp->exprsp()->addHereThisAsNext(nodep);
|
||||
}
|
||||
m_nestedNameFormatp = nodep;
|
||||
|
||||
m_smtName.insert(0, "." + name);
|
||||
// Some expressions have empty name which can cause some variables to have the same
|
||||
// name and only first one will be written
|
||||
if (name == "") {
|
||||
if (*m_firstExpressionInsideIndexPointerp) {
|
||||
(*m_firstExpressionInsideIndexPointerp)
|
||||
->v3warn(
|
||||
E_UNSUPPORTED,
|
||||
"Unsupported: Multiple expressions inside indices of complex "
|
||||
"expression in constraint\n"
|
||||
<< (*m_firstExpressionInsideIndexPointerp)->warnContextPrimary()
|
||||
<< nodep->warnOther() << "... Location of second expression\n"
|
||||
<< nodep->warnContextSecondary());
|
||||
}
|
||||
*m_firstExpressionInsideIndexPointerp = nodep;
|
||||
}
|
||||
}
|
||||
|
||||
AstMemberSel* accessTree() { return m_topNestedArrayMemberSelp; }
|
||||
const std::string& smtName() { return m_smtName; }
|
||||
|
||||
void write_var(AstNodeFTask* initTaskp, AstVar* varp, AstVar* genp) {
|
||||
AstCMethodHard* const methodp = new AstCMethodHard{
|
||||
varp->fileline(),
|
||||
new AstVarRef{varp->fileline(), VN_AS(genp->user2p(), NodeModule), genp,
|
||||
VAccess::READWRITE},
|
||||
VCMethod::RANDOMIZER_WRITE_VAR};
|
||||
methodp->dtypeSetVoid();
|
||||
|
||||
methodp->addPinsp(m_topNestedArrayMemberSelp); // Variable
|
||||
|
||||
const AstNodeDType* const dtypep = varp->dtypep(); // Width
|
||||
const size_t width = m_topNestedArrayMemberSelp->varp()->dtypep()->width();
|
||||
methodp->addPinsp(new AstConst{dtypep->fileline(), AstConst::Unsized64{}, width});
|
||||
|
||||
methodp->addPinsp(m_nestedNameFormatTopp); // Solver name
|
||||
m_nestedNameFormatp = nullptr;
|
||||
|
||||
methodp->addPinsp(
|
||||
new AstConst{dtypep->fileline(), AstConst::Unsized64{}, 0}); // Dimension
|
||||
|
||||
m_topNestedArrayMemberSelp = nullptr;
|
||||
initTaskp->addStmtsp(methodp->makeStmt());
|
||||
}
|
||||
|
||||
AstNodeExpr* cloneName() { return m_nestedNameFormatTopp->cloneTree(false); }
|
||||
|
||||
void error() {
|
||||
VL_DO_DANGLING(m_topNestedArrayMemberSelp->deleteTree(), m_topNestedArrayMemberSelp);
|
||||
}
|
||||
|
||||
NestedAccessPath(AstMemberSel* topMemberSelp, AstNode** pointerToFirstExprp)
|
||||
: m_topNestedArrayMemberSelp{topMemberSelp}
|
||||
, m_firstExpressionInsideIndexPointerp{pointerToFirstExprp} {}
|
||||
~NestedAccessPath() {
|
||||
if (m_nestedNameFormatp) {
|
||||
m_nestedNameFormatTopp->deleteTree();
|
||||
m_nestedNameFormatp = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
NestedAccessPath* m_nestedAccess = nullptr; // Indicates state of nested access
|
||||
|
||||
// Routes nested sub-objects with static rand vars when the outer class has none.
|
||||
AstVar* findStaticRandModeVarMember(AstClass* classp) const {
|
||||
@@ -1255,7 +1340,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
VCMethod::RANDOMIZER_CLEAR_VAR_DISABLED};
|
||||
enablep->dtypeSetVoid();
|
||||
AstNodeExpr* const nameExprp
|
||||
= new AstSFormatF{varp->fileline(), smtName, false, nullptr};
|
||||
= m_nestedAccess ? m_nestedAccess->cloneName()
|
||||
: new AstSFormatF{varp->fileline(), smtName, false, nullptr};
|
||||
nameExprp->dtypep(varp->dtypep());
|
||||
enablep->addPinsp(nameExprp);
|
||||
// rand_mode OFF: set disabled state
|
||||
@@ -1281,7 +1367,9 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
VAccess::READWRITE},
|
||||
VCMethod::RANDOMIZER_MARK_RANDC};
|
||||
markp->dtypeSetVoid();
|
||||
AstNodeExpr* const nameExprp = new AstSFormatF{varp->fileline(), smtName, false, nullptr};
|
||||
AstNodeExpr* const nameExprp
|
||||
= m_nestedAccess ? m_nestedAccess->cloneName()
|
||||
: new AstSFormatF{varp->fileline(), smtName, false, nullptr};
|
||||
nameExprp->dtypep(varp->dtypep());
|
||||
markp->addPinsp(nameExprp);
|
||||
initTaskp->addStmtsp(markp->makeStmt());
|
||||
@@ -1463,7 +1551,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
void createSolverVarHandle(AstVar* const varp, const bool structSelOrCMeth,
|
||||
const bool isGlobalConstrained, const RandomizeMode randMode,
|
||||
AstMemberSel* const memberselp, const std::string& smtName,
|
||||
AstNodeModule* const classOrPackagep) const {
|
||||
AstNodeModule* const classOrPackagep, AstNodeModule* const classp,
|
||||
AstNodeFTask* const initTaskp) const {
|
||||
uint32_t unpackedDims = 0;
|
||||
if (varp->dtypeSkipRefp()->isNonPackedArray()) {
|
||||
unpackedDims = varp->dtypep()->dimensions(false).second;
|
||||
@@ -1473,7 +1562,6 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
markStructConstrainedRandRecurse(varp->dtypeSkipRefp());
|
||||
unpackedDims = 1;
|
||||
}
|
||||
AstNodeModule* const classp = getLeftmostVarModulep(memberselp, varp);
|
||||
AstCMethodHard* const methodp = new AstCMethodHard{
|
||||
varp->fileline(),
|
||||
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
|
||||
@@ -1504,7 +1592,6 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
// Always call write_var (keeps variable in solver for constraint
|
||||
// evaluation), but toggle disabled state so the solver skips
|
||||
// write-back when rand_mode is off.
|
||||
AstNodeFTask* const initTaskp = getInitTaskp(varp, memberselp || structSelOrCMeth, classp);
|
||||
initTaskp->addStmtsp(methodp->makeStmt());
|
||||
if (varp->lifetime().isStatic() && randMode.usesMode) {
|
||||
AstCMethodHard* const markp = new AstCMethodHard{
|
||||
@@ -1531,7 +1618,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
AstMemberSel* memberselp, AstVar* const varp) {
|
||||
// Use AstSFormatF (not AstConst{String}) to prevent editFormat/V3Const
|
||||
// from reformatting the SMT variable name into a hex literal
|
||||
AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr};
|
||||
AstNodeExpr* exprp = new AstSFormatF{
|
||||
nodep->fileline(), m_nestedAccess ? nodep->name() : smtName, false, nullptr};
|
||||
if (!randMode.usesMode) {
|
||||
// Global constraints keep nodep alive for write_var processing
|
||||
if (!isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
@@ -1606,7 +1694,12 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
AstMemberSel* memberselp = nullptr;
|
||||
bool structSelOrCMeth = false;
|
||||
std::string smtName;
|
||||
if (VN_IS(nodep->backp(), MemberSel)) {
|
||||
if (m_nestedAccess) {
|
||||
m_nestedAccess->addVarNamePart(
|
||||
new AstSFormatF{varp->fileline(), nodep->name(), false, nullptr}, nodep->name());
|
||||
smtName = m_nestedAccess->smtName();
|
||||
memberselp = m_nestedAccess->accessTree();
|
||||
} else if (VN_IS(nodep->backp(), MemberSel)) {
|
||||
// Build complete path from topmost MemberSel
|
||||
AstNode* topMemberSel = nodep->backp();
|
||||
while (VN_IS(topMemberSel->backp(), MemberSel)) {
|
||||
@@ -1678,12 +1771,22 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
if (isClassRefArray && !memberselp) {
|
||||
AstNodeModule* const classp = getLeftmostVarModulep(memberselp, varp);
|
||||
AstNodeFTask* const initTaskp
|
||||
= getInitTaskp(varp, memberselp || structSelOrCMeth, classp);
|
||||
if (m_nestedAccess) {
|
||||
m_nestedAccess->write_var(initTaskp, varp, m_genp);
|
||||
if (isGlobalConstrained && memberselp && randMode.usesMode) {
|
||||
setRandMode(varp, memberselp, smtName, randMode, initTaskp);
|
||||
}
|
||||
// If randc, also emit markRandc() for cyclic tracking
|
||||
if (varp->isRandC()) { markRandc(varp, smtName, initTaskp); }
|
||||
} else if (isClassRefArray && !memberselp) {
|
||||
createSolverArrayHandle(varp, elemClassRefDtp, classOrPackagep,
|
||||
isUnpackedClassRefArray, smtName);
|
||||
} else {
|
||||
createSolverVarHandle(varp, structSelOrCMeth, isGlobalConstrained, randMode,
|
||||
memberselp, smtName, classOrPackagep);
|
||||
memberselp, smtName, classOrPackagep, classp, initTaskp);
|
||||
}
|
||||
} else {
|
||||
// Variable already written, clean up cloned memberselp if any
|
||||
@@ -1691,6 +1794,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
// Delete nodep if it's a global constraint (not deleted yet)
|
||||
if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
if (m_nestedAccess) { VL_DO_DANGLING(delete m_nestedAccess, m_nestedAccess); }
|
||||
}
|
||||
// Build popcount expansion: (x & 1) + ((x & 2) >> 1) + ...
|
||||
// argp is consumed; caller must clone if reusing.
|
||||
@@ -2159,7 +2263,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
}
|
||||
}
|
||||
if (newp && m_structSel && newp->name() == "(select %s %s)") { newp->name("%s.%s"); }
|
||||
if (!newp || !hoistRandModeOverSelect(newp, origp)) {
|
||||
if (!newp || !hoistRandModeOverSelectAndMember(newp, origp)) {
|
||||
VL_DO_DANGLING(origp->deleteTree(), origp);
|
||||
}
|
||||
}
|
||||
@@ -2183,6 +2287,26 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
indexIsRand = true;
|
||||
}
|
||||
});
|
||||
if (m_nestedAccess) {
|
||||
AstNodeExpr* const bitp = nodep->bitp()->cloneTreePure(false);
|
||||
AstNodeExpr* const bitFormatp
|
||||
= indexIsRand ? new AstSFormatF{bitp->fileline(), bitp->name(), false, nullptr}
|
||||
: new AstSFormatF{bitp->fileline(), "%x", false, bitp};
|
||||
AstSFormatF* const herep
|
||||
= new AstSFormatF{nodep->fileline(), "%s.%s", false, bitFormatp};
|
||||
if (AstSel* selp = VN_CAST(bitp, Sel)) {
|
||||
m_nestedAccess->addVarNamePart(herep, selp->fromp()->name());
|
||||
} else {
|
||||
m_nestedAccess->addVarNamePart(herep, bitp->name());
|
||||
}
|
||||
if (indexIsRand) {
|
||||
nodep->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: Randomized index in complex expression");
|
||||
m_nestedAccess->error();
|
||||
VL_DO_DANGLING(bitp->deleteTree(), bitp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (indexIsRand) {
|
||||
// Index depends on rand variable -- keep as SMT symbol.
|
||||
// Array index sort is 32-bit, so zero-extend narrower indices.
|
||||
@@ -2205,7 +2329,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
handle.relink(indexp);
|
||||
AstSFormatF* const newp = editSMT(nodep, nodep->fromp(), indexp);
|
||||
renameArrayExpr(newp);
|
||||
if (!newp || !hoistRandModeOverSelect(newp, origp)) {
|
||||
if (!newp || !hoistRandModeOverSelectAndMember(newp, origp)) {
|
||||
VL_DO_DANGLING(origp->deleteTree(), origp);
|
||||
}
|
||||
}
|
||||
@@ -2225,13 +2349,16 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
return classp && classp->user2p() == varp;
|
||||
}
|
||||
// Lift a rand_mode Cond above the (select ...) chain of a frozen array element.
|
||||
bool hoistRandModeOverSelect(AstSFormatF* newp, AstNodeExpr* origp) {
|
||||
bool hoistRandModeOverSelectAndMember(AstSFormatF* newp, AstNodeExpr* origp) {
|
||||
// Only for selects yielding a non-array element (full chains)
|
||||
if (VN_IS(origp->dtypep()->skipRefp(), UnpackArrayDType)) return false;
|
||||
// Walk nested "(select %s %s)" frames down to a mode-gating AstCond
|
||||
std::vector<AstSFormatF*> frames;
|
||||
AstCond* modeCondp = nullptr;
|
||||
for (AstSFormatF* curp = newp; curp && curp->name() == "(select %s %s)";) {
|
||||
for (AstSFormatF* curp = newp;
|
||||
curp
|
||||
&& (curp->name() == "(select %s %s)" || curp->name() == "%s.%s"
|
||||
|| curp->name().rfind("%s.", 0) == 0);) {
|
||||
AstNodeExpr* const firstp = curp->exprsp();
|
||||
frames.push_back(curp);
|
||||
if (AstCond* const condp = VN_CAST(firstp, Cond)) {
|
||||
@@ -2247,9 +2374,9 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
// Rebuild the select chain text around the SMT name, innermost first
|
||||
for (auto it = frames.rbegin(); it != frames.rend(); ++it) {
|
||||
AstNodeExpr* const idxFmtp = VN_AS((*it)->exprsp()->nextp(), NodeExpr);
|
||||
idxFmtp->unlinkFrBack();
|
||||
if (idxFmtp) idxFmtp->unlinkFrBack();
|
||||
activep
|
||||
= new AstSFormatF{fl, "(select %s %s)", false, AstNode::addNext(activep, idxFmtp)};
|
||||
= new AstSFormatF{fl, (*it)->name(), false, AstNode::addNext(activep, idxFmtp)};
|
||||
}
|
||||
AstCond* const hoistp = new AstCond{fl, modep, activep, getConstFormat(origp)};
|
||||
hoistp->user1(true); // Mark as formatted
|
||||
@@ -2260,43 +2387,47 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
void visit(AstMemberSel* nodep) override {
|
||||
// Check if rootVar is globalConstrained
|
||||
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
|
||||
FileLine* const fl = nodep->fileline();
|
||||
if (m_nestedAccess) {
|
||||
AstSFormatF* formatp = new AstSFormatF{fl, nodep->name(), false, nullptr};
|
||||
m_nestedAccess->addVarNamePart(new AstSFormatF{fl, "%s.%s", false, formatp},
|
||||
nodep->name());
|
||||
}
|
||||
AstNode* rootNode = nodep->fromp();
|
||||
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
|
||||
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel)) {
|
||||
rootNode = selp->fromp();
|
||||
if (AstArraySel* const arraySelp = VN_CAST(rootNode, ArraySel)) {
|
||||
}
|
||||
|
||||
AstNodeSel* arraySelp = VN_CAST(rootNode, ArraySel);
|
||||
|
||||
if (arraySelp) {
|
||||
AstNodeDType* const arrayDtp = arraySelp->fromp()->dtypep()->skipRefp();
|
||||
AstNodeDType* const elemDtp
|
||||
= arrayDtp->subDTypep() ? arrayDtp->subDTypep()->skipRefp() : nullptr;
|
||||
if (elemDtp && VN_IS(elemDtp, ClassRefDType)) {
|
||||
// Nested class ref arrays not yet supported
|
||||
const bool isSimple
|
||||
= nodep->fromp() == rootNode && VN_IS(arraySelp->fromp(), VarRef);
|
||||
if (!isSimple) {
|
||||
nodep->v3warn(
|
||||
E_UNSUPPORTED,
|
||||
"Unsupported: Nested array element access in global constraint");
|
||||
return;
|
||||
if (!isSimple && !m_nestedAccess) {
|
||||
m_nestedAccess = new NestedAccessPath(nodep->cloneTree(false),
|
||||
&m_firstExpressionInsideIndexp);
|
||||
AstSFormatF* const formatp
|
||||
= new AstSFormatF{fl, nodep->name(), false, nullptr};
|
||||
m_nestedAccess->addVarNamePart(
|
||||
new AstSFormatF{fl, "%s.%s", false, formatp}, nodep->name());
|
||||
}
|
||||
VL_RESTORER(m_structSel);
|
||||
m_structSel = true;
|
||||
nodep->user1(true);
|
||||
arraySelp->user1(true);
|
||||
AstNodeExpr* origp = nodep->cloneTree(false);
|
||||
iterateChildren(nodep);
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstSFormatF* newp = nullptr;
|
||||
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
|
||||
if (fromp->name() == "%s.%s") {
|
||||
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
|
||||
fromp->exprsp()->cloneTreePure(true)};
|
||||
} else {
|
||||
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
|
||||
nullptr};
|
||||
}
|
||||
} else {
|
||||
newp = new AstSFormatF{fl, nodep->name(), false, nullptr};
|
||||
}
|
||||
AstSFormatF* const newp = new AstSFormatF{fl, "%s." + nodep->name(), false,
|
||||
nodep->fromp()->unlinkFrBack()};
|
||||
nodep->replaceWith(newp);
|
||||
if (!hoistRandModeOverSelectAndMember(newp, origp))
|
||||
VL_DO_DANGLING(origp->deleteTree(), origp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
VL_DO_DANGLING(delete m_nestedAccess, m_nestedAccess);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2308,9 +2439,15 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
|
||||
AstVar* const constrainedVar = varRefp->varp();
|
||||
if (constrainedVar->globalConstrained()) {
|
||||
bool const beforeIterate = m_nestedAccess;
|
||||
// Global constraint - unwrap the MemberSel
|
||||
iterateChildren(nodep);
|
||||
nodep->replaceWith(nodep->fromp()->unlinkFrBack());
|
||||
if (beforeIterate) {
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%s." + nodep->name(), false,
|
||||
nodep->fromp()->unlinkFrBack()});
|
||||
} else {
|
||||
nodep->replaceWith(nodep->fromp()->unlinkFrBack());
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
@@ -2627,6 +2764,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
// call. Conditional occurrences (if / foreach / implication body)
|
||||
// are rewritten by extractConditionalDisableSofts() before this
|
||||
// visitor runs, so anything we see here is unconditional.
|
||||
VL_RESTORER(m_firstExpressionInsideIndexp);
|
||||
if (nodep->isDisableSoft()) {
|
||||
nodep->replaceWith(buildDisableSoftCallStmt(nodep));
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
@@ -2736,7 +2874,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||
}
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
if (origp && !hoistRandModeOverSelect(newp, origp)) {
|
||||
if (origp && !hoistRandModeOverSelectAndMember(newp, origp)) {
|
||||
VL_DO_DANGLING(origp->deleteTree(), origp);
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#!/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: 2025 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
if not test.have_solver:
|
||||
test.skip("No constraint solver installed")
|
||||
|
||||
test.compile()
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,201 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`define stop $stop
|
||||
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
`define check_rand(cl, field, cond) \
|
||||
begin \
|
||||
automatic longint prev_result; \
|
||||
automatic int ok; \
|
||||
if (!bit'(cl.randomize())) $stop; \
|
||||
prev_result = longint'(field); \
|
||||
if (!(cond)) $stop; \
|
||||
repeat(9) begin \
|
||||
longint result; \
|
||||
if (!bit'(cl.randomize())) $stop; \
|
||||
result = longint'(field); \
|
||||
if (!(cond)) $stop; \
|
||||
if (result != prev_result) ok = 1; \
|
||||
prev_result = result; \
|
||||
end \
|
||||
if (ok != 1) $stop; \
|
||||
end
|
||||
|
||||
/* verilator lint_off WIDTHTRUNC */
|
||||
class Inner;
|
||||
rand int m_x;
|
||||
rand int m_y;
|
||||
endclass
|
||||
|
||||
class Middle;
|
||||
rand Inner m_obj;
|
||||
rand Inner m_arr[3];
|
||||
endclass
|
||||
|
||||
class Item;
|
||||
rand int x;
|
||||
rand int y;
|
||||
randc bit [1:0] cycle;
|
||||
endclass
|
||||
|
||||
class Holder;
|
||||
rand Item cyclic[1];
|
||||
rand Item mode[1];
|
||||
rand Item items[2];
|
||||
rand Inner m_string_assoc[string];
|
||||
function new;
|
||||
mode[0] = new;
|
||||
cyclic[0] = new;
|
||||
m_string_assoc["abc"] = new;
|
||||
foreach (items[i])
|
||||
items[i] = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class Outer;
|
||||
int m_idx;
|
||||
rand Middle m_mid;
|
||||
rand Middle m_mid2;
|
||||
rand Middle m_mid_arr[3];
|
||||
rand Middle m_mid_arr2[3];
|
||||
rand Inner m_assoc[int];
|
||||
string m_key;
|
||||
rand Inner m_assoc_nested[int][bit];
|
||||
rand Holder m_holder;
|
||||
|
||||
function new();
|
||||
m_idx = 1;
|
||||
m_key = "abc";
|
||||
m_mid = new;
|
||||
m_mid.m_obj = new;
|
||||
foreach (m_mid.m_arr[i]) m_mid.m_arr[i] = new;
|
||||
m_mid2 = new;
|
||||
foreach (m_mid2.m_arr[i]) m_mid2.m_arr[i] = new;
|
||||
foreach (m_mid_arr[i]) begin
|
||||
m_mid_arr[i] = new;
|
||||
m_mid_arr[i].m_obj = new;
|
||||
foreach (m_mid_arr[i].m_arr[j]) m_mid_arr[i].m_arr[j] = new;
|
||||
end
|
||||
foreach (m_mid_arr2[i]) begin
|
||||
m_mid_arr2[i] = new;
|
||||
m_mid_arr2[i].m_obj = new;
|
||||
foreach (m_mid_arr2[i].m_arr[j]) m_mid_arr2[i].m_arr[j] = new;
|
||||
end
|
||||
|
||||
m_assoc[0] = new;
|
||||
m_assoc[1] = new;
|
||||
m_assoc_nested[123][1] = new;
|
||||
m_holder = new;
|
||||
endfunction
|
||||
|
||||
// Case 1: Simple nested member access
|
||||
constraint c_simple {
|
||||
m_mid.m_obj.m_x == 100;
|
||||
m_mid.m_obj.m_y == 101;
|
||||
}
|
||||
|
||||
// Case 2: Array indexing in the path
|
||||
constraint c_array_index {
|
||||
m_mid.m_arr[0].m_x == 200;
|
||||
m_mid.m_arr[0].m_y == 201;
|
||||
|
||||
m_mid2.m_arr[0].m_x < 200;
|
||||
m_mid2.m_arr[0].m_y < 201;
|
||||
}
|
||||
|
||||
constraint c_array_index_idx {
|
||||
m_mid.m_arr[m_idx].m_x == 202;
|
||||
m_mid.m_arr[m_idx].m_y == 203;
|
||||
|
||||
m_mid2.m_arr[m_idx].m_x < 202;
|
||||
m_mid2.m_arr[m_idx].m_y < 203;
|
||||
}
|
||||
|
||||
// Case 3: Nested array indexing
|
||||
constraint c_nested_array {
|
||||
m_mid_arr[0].m_obj.m_x == 300;
|
||||
m_mid_arr[0].m_obj.m_y == 301;
|
||||
}
|
||||
|
||||
// Case 4: Multiple array indices
|
||||
constraint c_multi_array {
|
||||
m_mid_arr[1].m_arr[2].m_y == 400;
|
||||
m_mid_arr[2].m_arr[2].m_y < 400;
|
||||
}
|
||||
|
||||
// Case 5: Nested array indexing with offset
|
||||
constraint c_nested_array_w_offset {
|
||||
m_mid_arr2[m_idx + 1].m_obj.m_x == 500;
|
||||
m_mid_arr2[m_idx + 1].m_obj.m_y == 501;
|
||||
}
|
||||
|
||||
// Case 6: randmode
|
||||
constraint c_mode {
|
||||
m_holder.mode[0].x == 42;
|
||||
}
|
||||
|
||||
// Case 7: randc
|
||||
constraint c_randc {
|
||||
m_holder.cyclic[0].cycle inside {[0:3]};
|
||||
}
|
||||
endclass
|
||||
|
||||
|
||||
module t_constraint_global_arr_nested;
|
||||
initial begin
|
||||
automatic Outer o = new;
|
||||
automatic Outer randc_o = new;
|
||||
automatic Outer randmode_off_o = new;
|
||||
automatic bit [3:0] seen = 0;
|
||||
automatic int rand_res;
|
||||
|
||||
rand_res = randmode_off_o.randomize();
|
||||
`checkd(rand_res, 1);
|
||||
`checkd(randmode_off_o.m_holder.mode[0].x, 42);
|
||||
|
||||
o.m_holder.mode[0].x = 42;
|
||||
o.m_holder.mode[0].x.rand_mode(0);
|
||||
|
||||
rand_res = o.randomize();
|
||||
`checkd(rand_res, 1);
|
||||
|
||||
`checkd(o.m_mid.m_obj.m_x, 100);
|
||||
`checkd(o.m_mid.m_obj.m_y, 101);
|
||||
|
||||
`checkd(o.m_mid.m_arr[0].m_x, 200);
|
||||
`checkd(o.m_mid.m_arr[0].m_y, 201);
|
||||
`checkd(o.m_mid.m_arr[1].m_x, 202);
|
||||
`checkd(o.m_mid.m_arr[1].m_y, 203);
|
||||
|
||||
`checkd(o.m_mid_arr[0].m_obj.m_x, 300);
|
||||
`checkd(o.m_mid_arr[0].m_obj.m_y, 301);
|
||||
`checkd(o.m_mid_arr[1].m_arr[2].m_y, 400);
|
||||
|
||||
`checkd(o.m_holder.mode[0].x, 42);
|
||||
|
||||
`checkd(o.m_mid_arr2[2].m_obj.m_x, 500);
|
||||
`checkd(o.m_mid_arr2[2].m_obj.m_y, 501);
|
||||
|
||||
`check_rand(o, o.m_mid2.m_arr[0].m_x, o.m_mid2.m_arr[0].m_x < 200);
|
||||
`check_rand(o, o.m_mid2.m_arr[0].m_y, o.m_mid2.m_arr[0].m_y < 201);
|
||||
|
||||
`check_rand(o, o.m_mid2.m_arr[1].m_x, o.m_mid2.m_arr[1].m_x < 202);
|
||||
`check_rand(o, o.m_mid2.m_arr[1].m_y, o.m_mid2.m_arr[1].m_y < 203);
|
||||
|
||||
`check_rand(o, o.m_mid_arr[2].m_arr[2].m_y, o.m_mid_arr[2].m_arr[2].m_y < 400);
|
||||
|
||||
repeat (4) begin
|
||||
rand_res = randc_o.randomize();
|
||||
`checkd(rand_res, 1);
|
||||
`checkd(seen[randc_o.m_holder.cyclic[0].cycle], 0);
|
||||
seen[randc_o.m_holder.cyclic[0].cycle] = 1;
|
||||
end
|
||||
`checkd(seen, 4'b1111);
|
||||
|
||||
$display("*-* All Finished *-*");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -1,34 +1,27 @@
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Nested array element access in global constraint
|
||||
41 | m_mid.m_arr[0].m_x == 200;
|
||||
| ^~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Nested array element access in global constraint
|
||||
42 | m_mid.m_arr[0].m_y == 201;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Nested array element access in global constraint
|
||||
47 | m_mid_arr[0].m_obj.m_x == 300;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Nested array element access in global constraint
|
||||
48 | m_mid_arr[0].m_obj.m_y == 301;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Nested array element access in global constraint
|
||||
53 | m_mid_arr[1].m_arr[2].m_y == 400;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:59:16: Unsupported: Array element access in global constraint
|
||||
59 | m_assoc[0].m_x == 500;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:65:34: Unsupported: Nested array element access in global constraint
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:44:34: Unsupported: Nested array element access in global constraint
|
||||
: ... note: In instance 't_constraint_global_arr_unsup'
|
||||
65 | solve m_mid_arr[((i*2)/2)].m_obj.m_x before m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
44 | solve m_mid_arr[((i*2)/2)].m_obj.m_x before m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:65:76: Unsupported: Nested array element access in global constraint
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:44:76: Unsupported: Nested array element access in global constraint
|
||||
: ... note: In instance 't_constraint_global_arr_unsup'
|
||||
65 | solve m_mid_arr[((i*2)/2)].m_obj.m_x before m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
44 | solve m_mid_arr[((i*2)/2)].m_obj.m_x before m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:67:34: Unsupported: Nested array element access in global constraint
|
||||
67 | m_mid_arr[((i*2)/2)].m_obj.m_x != m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:67:72: Unsupported: Nested array element access in global constraint
|
||||
67 | m_mid_arr[((i*2)/2)].m_obj.m_x != m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^~~
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:46:16: Unsupported: Multiple expressions inside indices of complex expression in constraint
|
||||
: ... note: In instance 't_constraint_global_arr_unsup'
|
||||
46 | m_mid_arr[((i*2)/2)].m_obj.m_x != m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^
|
||||
t/t_constraint_global_arr_unsup.v:46:50: ... Location of second expression
|
||||
46 | m_mid_arr[((i*2)/2)].m_obj.m_x != m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
| ^
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:52:14: Unsupported: Randomized index in complex expression
|
||||
52 | m_mid_arr[m_idx].m_obj.m_x == 123;
|
||||
| ^
|
||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:58:24: Unsupported: Array element access in global constraint
|
||||
58 | m_mid.m_assoc[i].m_x == 1;
|
||||
| ^~~
|
||||
%Error: t/t_constraint_global_arr_unsup.v:58:21: Illegal non-integral expression or subexpression in random constraint. (IEEE 1800-2023 18.3)
|
||||
58 | m_mid.m_assoc[i].m_x == 1;
|
||||
| ^
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: Exiting due to
|
||||
|
||||
@@ -10,14 +10,22 @@ class Inner;
|
||||
rand int m_y;
|
||||
endclass
|
||||
|
||||
typedef struct {
|
||||
int a;
|
||||
int b;
|
||||
} UnpackedIndexType;
|
||||
|
||||
class Middle;
|
||||
rand Inner m_obj;
|
||||
rand Inner m_arr[3];
|
||||
rand Inner m_assoc[UnpackedIndexType];
|
||||
endclass
|
||||
|
||||
class Outer;
|
||||
rand Middle m_mid;
|
||||
rand Middle m_mid_arr[2];
|
||||
rand int m_idx;
|
||||
int m_base = 0;
|
||||
|
||||
function new();
|
||||
m_mid = new;
|
||||
@@ -30,36 +38,7 @@ class Outer;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// Case 1: Simple nested member access (should work)
|
||||
constraint c_simple {
|
||||
m_mid.m_obj.m_x == 100;
|
||||
m_mid.m_obj.m_y == 101;
|
||||
}
|
||||
|
||||
// Case 2: Array indexing in the path (may not work)
|
||||
constraint c_array_index {
|
||||
m_mid.m_arr[0].m_x == 200;
|
||||
m_mid.m_arr[0].m_y == 201;
|
||||
}
|
||||
|
||||
// Case 3: Nested array indexing
|
||||
constraint c_nested_array {
|
||||
m_mid_arr[0].m_obj.m_x == 300;
|
||||
m_mid_arr[0].m_obj.m_y == 301;
|
||||
}
|
||||
|
||||
// Case 4: Multiple array indices
|
||||
constraint c_multi_array {
|
||||
m_mid_arr[1].m_arr[2].m_y == 400;
|
||||
}
|
||||
|
||||
// Case 5: Associative array element member access
|
||||
rand Inner m_assoc[int];
|
||||
constraint c_assoc {
|
||||
m_assoc[0].m_x == 500;
|
||||
}
|
||||
|
||||
// Case 6: Array elements member access in solve...before foreach loop
|
||||
// Case 1: Array elements member access in solve...before foreach loop
|
||||
constraint c_foreach {
|
||||
foreach (m_mid_arr[i]) {
|
||||
solve m_mid_arr[((i*2)/2)].m_obj.m_x before m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
@@ -67,32 +46,24 @@ class Outer;
|
||||
m_mid_arr[((i*2)/2)].m_obj.m_x != m_mid_arr[((i*2)/2) + 1].m_obj.m_x;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: Randomized index in nested array access
|
||||
constraint c_randomized_index {
|
||||
m_mid_arr[m_idx].m_obj.m_x == 123;
|
||||
}
|
||||
|
||||
// Unsupported expression inside index
|
||||
constraint c_bad_index {
|
||||
foreach(m_mid.m_assoc[i])
|
||||
m_mid.m_assoc[i].m_x == 1;
|
||||
}
|
||||
endclass
|
||||
|
||||
module t_constraint_global_arr_unsup;
|
||||
initial begin
|
||||
automatic Outer o = new;
|
||||
if (o.randomize()) begin
|
||||
$display("Case 1 - Simple: mid.obj.x = %0d (expected 100)", o.m_mid.m_obj.m_x);
|
||||
$display("Case 1 - Simple: mid.obj.y = %0d (expected 101)", o.m_mid.m_obj.m_y);
|
||||
$display("Case 2 - Array[0]: mid.arr[0].x = %0d (expected 200)", o.m_mid.m_arr[0].m_x);
|
||||
$display("Case 2 - Array[0]: mid.arr[0].y = %0d (expected 201)", o.m_mid.m_arr[0].m_y);
|
||||
$display("Case 3 - Nested[0]: mid_arr[0].obj.x = %0d (expected 300)", o.m_mid_arr[0].m_obj.m_x);
|
||||
$display("Case 3 - Nested[0]: mid_arr[0].obj.y = %0d (expected 301)", o.m_mid_arr[0].m_obj.m_y);
|
||||
$display("Case 4 - Multi[1][2]: mid_arr[1].arr[2].y = %0d (expected 400)", o.m_mid_arr[1].m_arr[2].m_y);
|
||||
|
||||
// Check results
|
||||
if (o.m_mid.m_obj.m_x == 100 && o.m_mid.m_obj.m_y == 101 &&
|
||||
o.m_mid.m_arr[0].m_x == 200 && o.m_mid.m_arr[0].m_y == 201 &&
|
||||
o.m_mid_arr[0].m_obj.m_x == 300 && o.m_mid_arr[0].m_obj.m_y == 301 &&
|
||||
o.m_mid_arr[1].m_arr[2].m_y == 400) begin
|
||||
$display("*-* All Finished *-*");
|
||||
$finish;
|
||||
end
|
||||
else begin
|
||||
$display("*-* FAILED *-*");
|
||||
$stop;
|
||||
end
|
||||
$display("*-* All Finished *-*");
|
||||
end
|
||||
else begin
|
||||
$display("*-* FAILED: randomize() returned 0 *-*");
|
||||
|
||||
Reference in New Issue
Block a user