diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index cd571805c..0acf11c60 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -2880,6 +2880,21 @@ class ConstraintExprVisitor final : public VNVisitor { return; } + // We've handled DYN_SIZE already if they should be randomized + if (nodep->method() == VCMethod::DYN_SIZE || nodep->method() == VCMethod::ASSOC_SIZE) { + AstNodeExpr* nodeExprp = nodep->fromp(); + // Check if it is a pure chain of MemberSel ended with VarRef + // if not throw warning + while (VN_IS(nodeExprp, MemberSel)) { + nodeExprp = VN_AS(nodeExprp, MemberSel)->fromp(); + } + if (VN_IS(nodeExprp, VarRef)) { + nodep->replaceWith(getConstFormat(nodep->cloneTreePure(false))); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + } + nodep->v3warn(CONSTRAINTIGN, "Unsupported: randomizing this expression, treating as state"); nodep->user1(false); @@ -3310,6 +3325,7 @@ public: // PUBLIC METHODS AstArg* getArgs() const { return m_argsp; } + AstVar* getThisp() const { return m_thisp; } void addFunctionArguments(AstNodeFTask* funcp) const { for (AstArg* argp = getArgs(); argp; argp = VN_AS(argp->nextp(), Arg)) { @@ -4324,10 +4340,16 @@ class RandomizeVisitor final : public VNVisitor { } // Build a size >= 0 constraint expression (signed int size must be non-negative). // Returns a new AstConstraintExpr with user1 bits set for solver visibility. - AstConstraintExpr* createSizeGteZeroConstraint(FileLine* const fl, AstVar* const sizeVarp) { - AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; - sizeVarRefp->user1(true); - AstGteS* const sizeGtep = new AstGteS{fl, sizeVarRefp, new AstConst{fl, 0}}; + AstConstraintExpr* createSizeGteZeroConstraint(FileLine* const fl, AstVar* const sizeVarp, + AstNodeExpr* derefFromp = nullptr) { + AstNodeExpr* sizeNodeExprp = nullptr; + if (derefFromp) { + sizeNodeExprp = buildMemberSelExprp(derefFromp, sizeVarp); + } else { + sizeNodeExprp = new AstVarRef{fl, sizeVarp, VAccess::READ}; + } + sizeNodeExprp->user1(true); + AstGteS* const sizeGtep = new AstGteS{fl, sizeNodeExprp, new AstConst{fl, 0}}; sizeGtep->user1(true); return new AstConstraintExpr{fl, sizeGtep}; } @@ -4402,6 +4424,27 @@ class RandomizeVisitor final : public VNVisitor { } return commonp; } + // Returns MemberSel chain, with deepest VarRef being reference to deepestVarp + AstNodeExpr* buildMemberSelExprp(AstNodeExpr* exprp, AstVar* deepestVarp) { + UASSERT_OBJ(VN_IS(exprp, MemberSel) || VN_IS(exprp, VarRef), exprp, + "Should be MemberSel or VarRef"); + + AstNodeExpr* resExprp = nullptr; + if (const AstMemberSel* memberSelp = VN_CAST(exprp, MemberSel)) { + resExprp = buildMemberSelExprp(memberSelp->fromp(), nullptr); + AstVar* varp = memberSelp->varp(); + if (deepestVarp) varp = deepestVarp; + resExprp = new AstMemberSel{exprp->fileline(), resExprp, varp}; + } else if (const AstVarRef* varRefp = VN_CAST(exprp, VarRef)) { + AstVar* varp = varRefp->varp(); + if (deepestVarp) varp = deepestVarp; + resExprp = new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), NodeModule), varp, + VAccess::READWRITE}; + } + + resExprp->user1(true); + return resExprp; + } void addBasicRandomizeBody(AstFunc* const basicRandomizep, AstClass* const nodep, AstVar* randModeVarp) { @@ -6011,42 +6054,52 @@ class RandomizeVisitor final : public VNVisitor { for (AstCMethodHard* const methodp : sizeMethodps) { // Extract array variable from fromp (VarRef or MemberSel after capture) AstVar* arrVarp = nullptr; - if (AstVarRef* const varRefp = VN_CAST(methodp->fromp(), VarRef)) { - arrVarp = varRefp->varp(); - } else if (AstMemberSel* const memberSelp = VN_CAST(methodp->fromp(), MemberSel)) { + AstNodeExpr* const rootExprp = methodp->fromp(); + if (AstMemberSel* memberSelp = VN_CAST(rootExprp, MemberSel)) { arrVarp = memberSelp->varp(); + while (VN_IS(memberSelp->fromp(), MemberSel)) { + memberSelp = VN_CAST(memberSelp->fromp(), MemberSel); + } + // Only variables that are part of object that randomize is called on shall be + // randomized (18.6.1), skip randomization if deepest variable is reference to + // __Vthis + const AstNodeExpr* fromp = memberSelp->fromp(); + if (const AstVarRef* varRefp = VN_CAST(fromp, VarRef)) { + if (varRefp->varp() == captured.getThisp()) continue; + } else { + fromp->v3warn(E_UNSUPPORTED, "Unsupported: Unsupported size constraint " + "inside 'with' statement of type " + << fromp->dtypep()->prettyTypeName()); + } + } else if (AstVarRef* const varRefp = VN_CAST(rootExprp, VarRef)) { + arrVarp = varRefp->varp(); } if (!arrVarp) continue; // Only handle rand-declared dynamic/assoc array variables if (!arrVarp->rand().isRandomizable()) continue; + // Do not randomize associative array size (IEEE 18.4) + if (VN_IS(arrVarp->dtypep()->skipRefp(), AssocArrayDType)) continue; FileLine* const fl = methodp->fileline(); bool wasCreated = false; - AstVar* const sizeVarp - = createOrGetSizeVar(classp, arrVarp, fl, methodp->findIntDType(), wasCreated); + AstClass* const varClassp = VN_AS(arrVarp->user2p(), Class); + AstVar* const sizeVarp = createOrGetSizeVar(varClassp, arrVarp, fl, + methodp->findIntDType(), wasCreated); // arrVarp and sizeVarp may live in a base class when the // array is inherited; route VarRefs through their declaring // class so V3Scope can resolve them. - AstNodeModule* const arrClassp = VN_AS(arrVarp->user2p(), NodeModule); - AstNodeModule* const sizeClassp = VN_AS(sizeVarp->user2p(), NodeModule); if (wasCreated) { - // Generate resize for dynamic arrays/queues (not assoc arrays) - if (!VN_IS(arrVarp->dtypep()->skipRefp(), AssocArrayDType)) { - AstCMethodHard* const resizep = new AstCMethodHard{ - fl, new AstVarRef{fl, arrClassp, arrVarp, VAccess::READWRITE}, - VCMethod::DYN_RESIZE, - new AstVarRef{fl, sizeClassp, sizeVarp, VAccess::READ}}; - resizep->dtypep(methodp->findVoidDType()); - inlineResizeStmtsp - = AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep}); - } - + AstNodeExpr* const resizeVarRefp = buildMemberSelExprp(rootExprp, arrVarp); + AstNodeExpr* const sizeExprp = buildMemberSelExprp(rootExprp, sizeVarp); // Append size >= 0 constraint so ConstraintExprVisitor processes it - capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp)); + capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp, rootExprp)); + AstCMethodHard* const resizep + = new AstCMethodHard{fl, resizeVarRefp, VCMethod::DYN_RESIZE, sizeExprp}; + resizep->dtypep(methodp->findVoidDType()); + inlineResizeStmtsp + = AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep}); } - AstVarRef* const sizeVarRefp - = new AstVarRef{fl, sizeClassp, sizeVarp, VAccess::READ}; - sizeVarRefp->user1(true); - methodp->replaceWith(sizeVarRefp); + AstNodeExpr* const sizeNodeExprp = buildMemberSelExprp(rootExprp, sizeVarp); + methodp->replaceWith(sizeNodeExprp); VL_DO_DANGLING(methodp->deleteTree(), methodp); } } diff --git a/test_regress/t/t_constraint_arr_with_arrsize_unsup.out b/test_regress/t/t_constraint_arr_with_arrsize_unsup.out new file mode 100644 index 000000000..7bdde5a97 --- /dev/null +++ b/test_regress/t/t_constraint_arr_with_arrsize_unsup.out @@ -0,0 +1,10 @@ +%Error-UNSUPPORTED: t/t_constraint_arr_with_arrsize_unsup.v:27:14: Unsupported: Unsupported size constraint inside 'with' statement of type CLASSREFDTYPE 'Sub' + : ... note: In instance 't' + 27 | if (i.s[0].arr.size > 0) { + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Internal Error: t/t_constraint_arr_with_arrsize_unsup.v:27:14: ../V3Randomize.cpp:#: Should be MemberSel or VarRef + : ... note: In instance 't' + 27 | if (i.s[0].arr.size > 0) { + | ^ + ... This fatal error may be caused by the earlier error(s); resolve those first. diff --git a/test_regress/t/t_constraint_arr_with_arrsize_unsup.py b/test_regress/t/t_constraint_arr_with_arrsize_unsup.py new file mode 100755 index 000000000..a00127d05 --- /dev/null +++ b/test_regress/t/t_constraint_arr_with_arrsize_unsup.py @@ -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=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_arr_with_arrsize_unsup.v b/test_regress/t/t_constraint_arr_with_arrsize_unsup.v new file mode 100644 index 000000000..00f58c83b --- /dev/null +++ b/test_regress/t/t_constraint_arr_with_arrsize_unsup.v @@ -0,0 +1,44 @@ +// 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 + +class Sub; + rand int arr[]; + + function new(); + arr = new[10]; + endfunction +endclass + +class Indep; + rand int val; + Sub s[]; + + function new(); + s = new [10]; + endfunction +endclass + +class Cls; + function int randomize_gpr(Indep i); + return (i.randomize() with { + if (i.s[0].arr.size > 0) { + i.val inside {i.s[0].arr}; + } + }); + endfunction +endclass + +module t; + Cls c; + Indep i; + + initial begin + c = new; + i = new; + + c.randomize_gpr(i); + end +endmodule diff --git a/test_regress/t/t_constraint_dyn_size_class.py b/test_regress/t/t_constraint_dyn_size_class.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dyn_size_class.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_dyn_size_class.v b/test_regress/t/t_constraint_dyn_size_class.v new file mode 100644 index 000000000..d7b2c02ed --- /dev/null +++ b/test_regress/t/t_constraint_dyn_size_class.v @@ -0,0 +1,128 @@ +// 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 + +/*verilator lint_off*/ +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`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); +/*verilator lint_on*/ + +class SubSubClass; + rand int subVal; + rand int subArr[]; + + constraint c{ + subArr.size < 10; + } +endclass + +class SubClass; + rand int subVal; + rand int subArr[]; + rand int assocArr[string] = '{ "test": 1, "test2" : 2 }; + SubSubClass ssc; + + function new(); + ssc = new; + ssc.randomize(); + endfunction +endclass + +class IndepClass; + rand int val0; + rand int val1; + rand int val2; + rand SubClass sc; + + function new(); + sc = new; + endfunction +endclass + +class BaseClass; + rand int arr[]; + // Size-constrained array + constraint bc { + arr.size < 10; + arr.size > 5; + }; +endclass + +class ExtClass0 extends BaseClass; + function int randomize_gpr(IndepClass cls); + return (cls.randomize() with { + // If with arr.size on array, that's a field of class + // calling randomize() with + if (arr.size > 0) { + val0 inside {arr}; + } + + // If with arr.size on array thats a field inside subclass + // chain + if (cls.sc.ssc.subArr.size > 0 ) { + val1 == 'hDEADBEEF; + } + + // Randomizable array size + sc.subArr.size > 25; + sc.subArr.size < 50; + + // If with associative array size + if (sc.assocArr.size > 0) { + val2 == 'hCAFEBABE; + } + }); + endfunction +endclass + +class ExtClass1 extends BaseClass; + // Class that extends BaseClass and uses arr.size variable + constraint c { + unique{arr}; + foreach(arr[i]) { + arr[i] != 0; + } + } +endclass + +module t; + ExtClass0 ext0; + ExtClass1 ext1; + IndepClass indep; + + initial begin + indep = new; + ext0 = new; + ext1 = new; + repeat(10) begin + `checkh(ext0.randomize(), 1); + `checkh(ext0.randomize(), 1); + `checkh(ext0.randomize_gpr(indep), 1); + + `checkh(indep.val0 inside {ext0.arr}, 1); + `checkh(indep.val1, 'hDEADBEEF); + `checkh(indep.val2, 'hCAFEBABE); + + `check_range(ext0.arr.size(), 5, 10); + `check_range(indep.sc.subArr.size(), 25, 50); + + foreach (ext0.arr[i]) begin + if (ext0.arr[i] == 0) begin + `stop; + end + foreach (ext0.arr[j]) begin + if (i == j) continue; + if (ext0.arr[i] == ext0.arr[j]) begin + `stop; + end + end + end + end + + $write("*-* All Finished *-*\n"); + $finish(); + end +endmodule diff --git a/test_regress/t/t_randomize_with_constraint.v b/test_regress/t/t_randomize_with_constraint.v index 8fcde8101..e3e185410 100644 --- a/test_regress/t/t_randomize_with_constraint.v +++ b/test_regress/t/t_randomize_with_constraint.v @@ -10,11 +10,30 @@ `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 +class ArgCls; + rand int b; +endclass + class Cls; rand int m_x; rand int m_z; int y = -1; // class member named 'y' intentionally shadows the caller arg int lo = -100; // class member named 'lo' intentionally shadows the caller arg + rand int a[]; + + function new(); + a = new [10]; + endfunction + + // Randomize array inside class that is argument + // with .size inside if + function automatic int randomize_subcls(ArgCls ac); + return ac.randomize() with { + if (a.size() > 0) { + ac.b == a.size(); + } + }; + endfunction endclass // 'y' is not in the list -> resolves to the caller arg (10), not class member (-1). @@ -76,8 +95,10 @@ endfunction module t; initial begin Cls c; + ArgCls ac; int i; c = new; + ac = new; repeat (20) begin i = func_restricted(c, 10); `checkd(i, 1); @@ -98,6 +119,11 @@ module t; `check_range(c.m_x, 1, 7); i = func_sequential(c, 6); `checkd(i, 1); + i = c.randomize_subcls(ac); + `checkd(i, 1); + if (c.a.size() > 0) begin + `checkd(ac.b, c.a.size()); + end // Statement form: discards return value via void'. void'(c.randomize() with (m_x) { m_x > 0; m_x < 5; }); `check_range(c.m_x, 1, 4);