From d57324e5fbbd2b6a817d8ac16f425b901ebf89d0 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 16 Mar 2026 23:48:36 +0100 Subject: [PATCH] Support dynamic array .size in inline randomize() with constraints (#7258) (#7266) --- src/V3Randomize.cpp | 107 +++++++++++++++--- .../t/t_constraint_dyn_size_inline.py | 21 ++++ test_regress/t/t_constraint_dyn_size_inline.v | 71 ++++++++++++ .../t/t_randomize_method_types_unsup.out | 3 - 4 files changed, 184 insertions(+), 18 deletions(-) create mode 100755 test_regress/t/t_constraint_dyn_size_inline.py create mode 100644 test_regress/t/t_constraint_dyn_size_inline.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 169a3247b..844be5753 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3572,6 +3572,32 @@ class RandomizeVisitor final : public VNVisitor { UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef"); return new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), Class), varp, access}; } + // Get or create a size variable for a constrained dynamic/queue/assoc array. + // Returns the size variable. Sets wasCreated=true if a new variable was made. + AstVar* createOrGetSizeVar(AstClass* const classp, AstVar* const arrVarp, FileLine* const fl, + AstNodeDType* const signed32DTypep, bool& wasCreated) { + AstVar* sizeVarp = VN_CAST(arrVarp->user4p(), Var); + wasCreated = false; + if (!sizeVarp) { + sizeVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__V" + arrVarp->name() + "_size", + signed32DTypep}; + classp->addMembersp(sizeVarp); + m_memberMap.insert(classp, sizeVarp); + sizeVarp->user2p(classp); + arrVarp->user4p(sizeVarp); + wasCreated = true; + } + return sizeVarp; + } + // 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}}; + sizeGtep->user1(true); + return new AstConstraintExpr{fl, sizeGtep}; + } AstNodeExpr* getFromp(AstNodeExpr* const exprp) { if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { return memberSelp->fromp(); @@ -4719,6 +4745,57 @@ class RandomizeVisitor final : public VNVisitor { // Generate constraint setup code and a hardcoded call to the solver AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); randomizeFuncp->addStmtsp(capturedTreep); + + // Pre-scan captured tree for .size on dynamic/assoc arrays and replace + // with size variables, using shared createOrGetSizeVar helper. + // After CaptureVisitor, fromp() of .size is AstMemberSel (not VarRef), + // so extract the underlying variable from the MemberSel chain. + // Collect resize statements to add after solver call, guarded by success. + AstNode* inlineResizeStmtsp = nullptr; + { + std::vector sizeMethodps; + capturedTreep->foreachAndNext([&](AstCMethodHard* methodp) { + if (methodp->method() == VCMethod::DYN_SIZE + || methodp->method() == VCMethod::ASSOC_SIZE) { + sizeMethodps.push_back(methodp); + } + }); + 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)) { + arrVarp = memberSelp->varp(); + } + if (!arrVarp) continue; + // Only handle rand-declared dynamic/assoc array variables + if (!arrVarp->rand().isRandomizable()) continue; + FileLine* const fl = methodp->fileline(); + bool wasCreated = false; + AstVar* const sizeVarp = createOrGetSizeVar( + classp, arrVarp, fl, methodp->findSigned32DType(), wasCreated); + 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, classp, arrVarp, VAccess::READWRITE}, + VCMethod::DYN_RESIZE, new AstVarRef{fl, sizeVarp, VAccess::READ}}; + resizep->dtypep(methodp->findVoidDType()); + inlineResizeStmtsp + = AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep}); + } + + // Append size >= 0 constraint so ConstraintExprVisitor processes it + capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp)); + } + AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; + sizeVarRefp->user1(true); + methodp->replaceWith(sizeVarRefp); + VL_DO_DANGLING(methodp->deleteTree(), methodp); + } + } + { expandUniqueElementList(capturedTreep); ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp, @@ -4735,6 +4812,16 @@ class RandomizeVisitor final : public VNVisitor { new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE}, new AstAnd{nodep->fileline(), basicRandomizeFuncCallp, solverCallp}}); + // Resize constrained arrays only when solver succeeds, mirroring + // the class-level if(__Vsize_ok) guard pattern + if (inlineResizeStmtsp) { + AstVar* const retVarp = VN_AS(randomizeFuncp->fvarp(), Var); + AstIf* const ifp = new AstIf{nodep->fileline(), + new AstVarRef{nodep->fileline(), retVarp, VAccess::READ}, + inlineResizeStmtsp, nullptr}; + randomizeFuncp->addStmtsp(ifp); + } + // Call nested post_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(classp)) { AstTask* const postTaskp = getCreateNestedCallbackTask(classp, "post"); @@ -4774,16 +4861,10 @@ class RandomizeVisitor final : public VNVisitor { return; } AstVar* const queueVarp = queueVarRefp->varp(); - AstVar* sizeVarp = VN_CAST(queueVarp->user4p(), Var); - if (!sizeVarp) { - sizeVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__V" + queueVarp->name() + "_size", - nodep->findSigned32DType()}; - classp->addMembersp(sizeVarp); - m_memberMap.insert(classp, sizeVarp); - sizeVarp->user2p(classp); - - queueVarp->user4p(sizeVarp); - + bool wasCreated = false; + AstVar* const sizeVarp = createOrGetSizeVar(classp, queueVarp, fl, + nodep->findSigned32DType(), wasCreated); + if (wasCreated) { // Associative arrays have no resize(); only generate resize // for dynamic arrays and queues if (!VN_IS(queueVarp->dtypep()->skipRefp(), AssocArrayDType)) { @@ -4802,11 +4883,7 @@ class RandomizeVisitor final : public VNVisitor { // Since size variable is signed int, we need additional constraint // to make sure it is always >= 0. - AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; - sizeVarRefp->user1(true); - AstGteS* const sizeGtep = new AstGteS{fl, sizeVarRefp, new AstConst{fl, 0}}; - sizeGtep->user1(true); - m_constraintp->addItemsp(new AstConstraintExpr{fl, sizeGtep}); + m_constraintp->addItemsp(createSizeGteZeroConstraint(fl, sizeVarp)); } AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; sizeVarRefp->user1(true); diff --git a/test_regress/t/t_constraint_dyn_size_inline.py b/test_regress/t/t_constraint_dyn_size_inline.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dyn_size_inline.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_inline.v b/test_regress/t/t_constraint_dyn_size_inline.v new file mode 100644 index 000000000..f1a780cad --- /dev/null +++ b/test_regress/t/t_constraint_dyn_size_inline.v @@ -0,0 +1,71 @@ +// 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 + +// verilog_format: 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); +// verilog_format: on + +class Packet; + rand int unsigned m_length; + rand byte m_data[]; + rand byte m_mask[]; + + constraint c_length { m_length inside {[1:16]}; } +endclass + +class MultiArray; + rand int unsigned m_len_a; + rand int unsigned m_len_b; + rand int m_arr_a[]; + rand int m_arr_b[]; + + constraint c_lens { + m_len_a inside {[2:8]}; + m_len_b inside {[3:10]}; + } +endclass + +module t; + initial begin + automatic Packet pkt = new; + automatic MultiArray ma = new; + automatic int ok; + + // Scenario 1: single class with two dynamic arrays sized by one field + repeat (5) begin + ok = pkt.randomize() with { + m_data.size == m_length; + m_mask.size == m_length; + foreach (m_mask[i]) m_mask[i] inside {8'h00, 8'hFF}; + }; + `checkh(ok, 1) + `checkh(pkt.m_data.size(), pkt.m_length) + `checkh(pkt.m_mask.size(), pkt.m_length) + // Verify mask values are constrained + for (int i = 0; i < pkt.m_mask.size(); i++) begin + if (pkt.m_mask[i] !== 8'h00 && pkt.m_mask[i] !== 8'hFF) begin + $write("%%Error: m_mask[%0d]=%0h not in {00, FF}\n", i, pkt.m_mask[i]); + `stop; + end + end + end + + // Scenario 2: two arrays with independent size constraints + repeat (5) begin + ok = ma.randomize() with { + m_arr_a.size == m_len_a; + m_arr_b.size == m_len_b; + }; + `checkh(ok, 1) + `checkh(ma.m_arr_a.size(), ma.m_len_a) + `checkh(ma.m_arr_b.size(), ma.m_len_b) + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randomize_method_types_unsup.out b/test_regress/t/t_randomize_method_types_unsup.out index 0697047ac..5f9b0bbd9 100644 --- a/test_regress/t/t_randomize_method_types_unsup.out +++ b/test_regress/t/t_randomize_method_types_unsup.out @@ -8,7 +8,4 @@ 15 | rand Cls cls; | ^~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:39:41: Unsupported: randomizing this expression, treating as state - 39 | res = obj.randomize() with { dynarr.size > 2; }; - | ^~~~ %Error: Exiting due to