diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 4e5e25eff..57c257796 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1021,8 +1021,8 @@ class ConstraintExprVisitor final : public VNVisitor { // For global constraints, delete nodep after processing if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep); - // Detect if variable is an array of class references bool isClassRefArray = false; + bool isUnpackedClassRefArray = false; AstClassRefDType* elemClassRefDtp = nullptr; { AstNodeDType* varDtp = varp->dtypep()->skipRefp(); @@ -1030,12 +1030,14 @@ class ConstraintExprVisitor final : public VNVisitor { || VN_IS(varDtp, UnpackArrayDType) || VN_IS(varDtp, AssocArrayDType)) { AstNodeDType* const elemDtp = varDtp->subDTypep()->skipRefp(); elemClassRefDtp = VN_CAST(elemDtp, ClassRefDType); - if (elemClassRefDtp) isClassRefArray = true; + if (elemClassRefDtp) { + isClassRefArray = true; + isUnpackedClassRefArray = VN_IS(varDtp, UnpackArrayDType); + } } } if (isClassRefArray && !membersel) { - // Per-member registration loop for class ref arrays FileLine* const fl = varp->fileline(); AstClass* const elemClassp = elemClassRefDtp->classp(); AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule); @@ -1049,22 +1051,36 @@ class ConstraintExprVisitor final : public VNVisitor { stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE}, new AstConst{fl, 0}}); - AstVarRef* const arraySizeRef = new AstVarRef{fl, varClassp, varp, VAccess::READ}; - arraySizeRef->classOrPackagep(classOrPackagep); - AstCMethodHard* const sizep - = new AstCMethodHard{fl, arraySizeRef, VCMethod::DYN_SIZE, nullptr}; - sizep->dtypeSetUInt32(); + AstNodeExpr* sizep; + if (isUnpackedClassRefArray) { + const int arraySize + = VN_AS(varp->dtypep()->skipRefp(), UnpackArrayDType)->elementsConst(); + sizep = new AstConst{fl, static_cast(arraySize)}; + } else { + AstVarRef* const arraySizeRef + = new AstVarRef{fl, varClassp, varp, VAccess::READ}; + arraySizeRef->classOrPackagep(classOrPackagep); + AstCMethodHard* const dynSizep + = new AstCMethodHard{fl, arraySizeRef, VCMethod::DYN_SIZE, nullptr}; + dynSizep->dtypeSetUInt32(); + sizep = dynSizep; + } AstLoop* const loopp = new AstLoop{fl}; stmtsp->addNext(loopp); loopp->addStmtsp(new AstLoopTest{ fl, loopp, new AstLt{fl, new AstVarRef{fl, iterVarp, VAccess::READ}, sizep}}); - AstVarRef* const arrayAtRef = new AstVarRef{fl, varClassp, varp, VAccess::READ}; - arrayAtRef->classOrPackagep(classOrPackagep); - AstCMethodHard* const atReadp - = new AstCMethodHard{fl, arrayAtRef, VCMethod::ARRAY_AT, - new AstVarRef{fl, iterVarp, VAccess::READ}}; + AstVarRef* const arrayReadRef = new AstVarRef{fl, varClassp, varp, VAccess::READ}; + arrayReadRef->classOrPackagep(classOrPackagep); + AstNodeExpr* atReadp; + if (isUnpackedClassRefArray) { + atReadp = new AstArraySel{fl, arrayReadRef, + new AstVarRef{fl, iterVarp, VAccess::READ}}; + } else { + atReadp = new AstCMethodHard{fl, arrayReadRef, VCMethod::ARRAY_AT, + new AstVarRef{fl, iterVarp, VAccess::READ}}; + } atReadp->dtypep(elemClassRefDtp); AstIf* const ifNonNullp = new AstIf{ fl, new AstNeq{fl, atReadp, new AstConst{fl, AstConst::Null{}}}, nullptr}; @@ -1090,9 +1106,14 @@ class ConstraintExprVisitor final : public VNVisitor { const int memberWidth = memberDtp->width(); AstCStmt* const fmtp = new AstCStmt{fl}; - fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%0" - + std::to_string(fmtWidth) + "x." + memberVarp->name() - + "\", (unsigned)"); + if (isUnpackedClassRefArray) { + fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%x." + + memberVarp->name() + "\", (unsigned)"); + } else { + fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%0" + + std::to_string(fmtWidth) + "x." + memberVarp->name() + + "\", (unsigned)"); + } fmtp->add(new AstVarRef{fl, iterVarp, VAccess::READ}); fmtp->add(");\n"); ifNonNullp->addThensp(fmtp); @@ -1100,9 +1121,15 @@ class ConstraintExprVisitor final : public VNVisitor { AstVarRef* const arrayWrRef = new AstVarRef{fl, varClassp, varp, VAccess::WRITE}; arrayWrRef->classOrPackagep(classOrPackagep); - AstCMethodHard* const atWritep - = new AstCMethodHard{fl, arrayWrRef, VCMethod::ARRAY_AT_WRITE, - new AstVarRef{fl, iterVarp, VAccess::READ}}; + AstNodeExpr* atWritep; + if (isUnpackedClassRefArray) { + atWritep = new AstArraySel{fl, arrayWrRef, + new AstVarRef{fl, iterVarp, VAccess::READ}}; + } else { + atWritep + = new AstCMethodHard{fl, arrayWrRef, VCMethod::ARRAY_AT_WRITE, + new AstVarRef{fl, iterVarp, VAccess::READ}}; + } atWritep->dtypep(elemClassRefDtp); AstMemberSel* const memberSelp = new AstMemberSel{fl, atWritep, memberVarp}; @@ -1635,10 +1662,46 @@ class ConstraintExprVisitor final : public VNVisitor { AstNode* rootNode = nodep->fromp(); while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel)) rootNode = selp->fromp(); - // Detect array/assoc array access in global constraints - if (VN_IS(rootNode, ArraySel) || VN_IS(rootNode, AssocSel)) { + if (AstArraySel* const arraySelp = VN_CAST(rootNode, ArraySel)) { + 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; + } + VL_RESTORER(m_structSel); + m_structSel = true; + nodep->user1(true); + arraySelp->user1(true); + iterateChildren(nodep); + FileLine* const fl = nodep->fileline(); + AstSFormatF* newp = nullptr; + if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) { + if (fromp->name() == "%@.%@") { + newp = new AstSFormatF{fl, "%@.%@." + 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}; + } + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + if (VN_IS(rootNode, AssocSel) || VN_IS(rootNode, ArraySel)) { nodep->v3warn(E_UNSUPPORTED, - "Unsupported: Array element access in global constraint "); + "Unsupported: Array element access in global constraint"); } // Check if the root variable participates in global constraints if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) { diff --git a/test_regress/t/t_constraint_cls_arr_member.py b/test_regress/t/t_constraint_cls_arr_member.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_cls_arr_member.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: 2026 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_cls_arr_member.v b/test_regress/t/t_constraint_cls_arr_member.v new file mode 100644 index 000000000..87fac6d50 --- /dev/null +++ b/test_regress/t/t_constraint_cls_arr_member.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// Test: constraints on class object array element members (fixed-size arrays) + +// verilog_format: off +`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_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=[%0d:%0d]\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +// verilog_format: on + +/* verilator lint_off WIDTHTRUNC */ +/* verilator lint_off IMPLICITSTATIC */ + +class item_t; + rand bit [7:0] value; +endclass + +// Scenario A: foreach with range constraint +class container_a; + rand item_t items[4]; + constraint val_c { + foreach (items[i]) { + items[i].value inside {[10:200]}; + } + } + function new(); + foreach (items[i]) items[i] = new; + endfunction +endclass + +// Scenario B: foreach with ordering constraint +class container_b; + rand item_t items[4]; + constraint order_c { + foreach (items[i]) { + if (i != 0) { + items[i].value > items[i-1].value; + } + } + } + function new(); + foreach (items[i]) items[i] = new; + endfunction +endclass + +// Scenario C: constant-index constraint (no foreach) +class container_c; + rand item_t items[4]; + constraint val_c { + items[0].value < items[1].value; + items[1].value < items[2].value; + items[2].value < items[3].value; + } + function new(); + foreach (items[i]) items[i] = new; + endfunction +endclass + +module t; + initial begin + automatic container_a ca = new; + automatic container_b cb = new; + automatic container_c cc = new; + automatic int ok; + + repeat (20) begin + // Scenario A: foreach range + ok = ca.randomize(); + `checkd(ok, 1); + for (int i = 0; i < 4; i++) begin + `check_range(ca.items[i].value, 10, 200); + end + + // Scenario B: foreach ordering + ok = cb.randomize(); + `checkd(ok, 1); + for (int i = 1; i < 4; i++) begin + if (cb.items[i].value <= cb.items[i-1].value) begin + $write("%%Error: %s:%0d: ordering violated: items[%0d]=%0d <= items[%0d]=%0d\n", + `__FILE__, `__LINE__, i, cb.items[i].value, i-1, cb.items[i-1].value); + `stop; + end + end + + // Scenario C: constant-index ordering + ok = cc.randomize(); + `checkd(ok, 1); + for (int i = 1; i < 4; i++) begin + if (cc.items[i].value <= cc.items[i-1].value) begin + $write("%%Error: %s:%0d: ordering violated: items[%0d]=%0d <= items[%0d]=%0d\n", + `__FILE__, `__LINE__, i, cc.items[i].value, i-1, cc.items[i-1].value); + `stop; + end + end + end + + $write("*-* All Finished *-*\n"); + $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 636a8f10a..a7320cc44 100644 --- a/test_regress/t/t_constraint_global_arr_unsup.out +++ b/test_regress/t/t_constraint_global_arr_unsup.out @@ -1,26 +1,20 @@ -%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Array element access in global constraint +%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: Array element access in global constraint +%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: Array element access in global constraint +%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:47:18: Unsupported: 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: Array element access in global constraint +%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:48:18: Unsupported: 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: Array element access in global constraint +%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:53:18: Unsupported: 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: 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 268e3ca0f..2f611c792 100644 --- a/test_regress/t/t_constraint_global_arr_unsup.v +++ b/test_regress/t/t_constraint_global_arr_unsup.v @@ -52,6 +52,12 @@ class Outer; 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; + } endclass module t_constraint_global_arr_unsup;