From f954f4fd1df3c9b30bf40a9834c81a15f6e7ea75 Mon Sep 17 00:00:00 2001 From: Kamil Danecki Date: Mon, 21 Sep 2026 14:10:53 +0200 Subject: [PATCH] Support nested array element member access in constraint (#8243) --- src/V3Randomize.cpp | 214 ++++++++++++++---- .../t/t_constraint_global_arr_nested.py | 21 ++ .../t/t_constraint_global_arr_nested.v | 201 ++++++++++++++++ .../t/t_constraint_global_arr_unsup.out | 51 ++--- .../t/t_constraint_global_arr_unsup.v | 71 ++---- 5 files changed, 441 insertions(+), 117 deletions(-) create mode 100755 test_regress/t/t_constraint_global_arr_nested.py create mode 100644 test_regress/t/t_constraint_global_arr_nested.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 2edbff692..a2f9d2993 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -786,6 +786,91 @@ class ConstraintExprVisitor final : public VNVisitor { std::set* 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 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); } diff --git a/test_regress/t/t_constraint_global_arr_nested.py b/test_regress/t/t_constraint_global_arr_nested.py new file mode 100755 index 000000000..8862c2c31 --- /dev/null +++ b/test_regress/t/t_constraint_global_arr_nested.py @@ -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() diff --git a/test_regress/t/t_constraint_global_arr_nested.v b/test_regress/t/t_constraint_global_arr_nested.v new file mode 100644 index 000000000..3b76cc4af --- /dev/null +++ b/test_regress/t/t_constraint_global_arr_nested.v @@ -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 diff --git a/test_regress/t/t_constraint_global_arr_unsup.out b/test_regress/t/t_constraint_global_arr_unsup.out index 0c2c5d221..9d4598641 100644 --- a/test_regress/t/t_constraint_global_arr_unsup.out +++ b/test_regress/t/t_constraint_global_arr_unsup.out @@ -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 diff --git a/test_regress/t/t_constraint_global_arr_unsup.v b/test_regress/t/t_constraint_global_arr_unsup.v index d4a6e1e44..6151d7e0e 100644 --- a/test_regress/t/t_constraint_global_arr_unsup.v +++ b/test_regress/t/t_constraint_global_arr_unsup.v @@ -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 *-*");