From 47b9fba5e54f2098994065d19938da1a6ab4d863 Mon Sep 17 00:00:00 2001 From: Kornel Uriasz Date: Tue, 8 Sep 2026 15:11:02 +0200 Subject: [PATCH] Fix array-size constraint on arrays inside base cass (#8219) Signed-off-by: Kornel Uriasz --- src/V3Randomize.cpp | 135 +++++++------ test_regress/t/t_constraint_dyn_size_class.v | 201 +++++++++++++++++++ 2 files changed, 280 insertions(+), 56 deletions(-) diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index f04fcd9e4..f44e1745b 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -5408,6 +5408,66 @@ class RandomizeVisitor final : public VNVisitor { return false; } + // Add write_var calls for each array element after resizing it with array-size constraint + void addArrayElemRefresh(FileLine* const fl, AstVar* const arrVarp, AstFunc* const randomizep, + AstVar* const genp) { + AstNodeModule* const genModp = VN_AS(genp->user2p(), NodeModule); + // Array elements of class data type are passed to the solver as separate + // variables, so passing the original array variable is redundant, because it + // won't be referenced + const uint32_t unpackedDims = arrVarp->dtypep()->dimensions(false).second; + if (isDynArrOfClassTypeRecurse(arrVarp->dtypep())) { + if (unpackedDims > 1) { + arrVarp->v3warn(E_UNSUPPORTED, + "Unsupported: Nested array element access in global constraint"); + } + return; + } + AstCMethodHard* const methodp + = new AstCMethodHard{fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, + VCMethod::RANDOMIZER_WRITE_VAR}; + methodp->dtypeSetVoid(); + + AstNodeModule* const classp = VN_AS(arrVarp->user2p(), NodeModule); + AstVarRef* const varRefp = new AstVarRef{fl, classp, arrVarp, VAccess::WRITE}; + varRefp->classOrPackagep(classp); + methodp->addPinsp(varRefp); + + const size_t width = arrayElementDTypep(arrVarp->dtypep())->width(); + + methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, width}); + AstNodeExpr* const varnamep + = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + arrVarp->name() + "\"", arrVarp->width()}; + varnamep->dtypep(arrVarp->dtypep()); + methodp->addPinsp(varnamep); + methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, unpackedDims}); + + randomizep->addStmtsp(methodp->makeStmt()); + } + + void pinSizeVariable(FileLine* const fl, AstVar* const arrVarp, AstFunc* const randomizep, + AstVar* const genp) { + AstNodeModule* const genModp = VN_AS(genp->user2p(), NodeModule); + AstVar* const sizeVarp = VN_CAST(arrVarp->user4p(), Var); + if (!sizeVarp) return; + AstCMethodHard* const pinp + = new AstCMethodHard{fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, + VCMethod::RANDOMIZER_PIN_VAR}; + pinp->dtypeSetVoid(); + AstCExpr* const namep = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + sizeVarp->name() + "\""}; + namep->dtypeSetUInt32(); + pinp->addPinsp(namep); + pinp->addPinsp( + new AstConst{fl, AstConst::Unsized64{}, static_cast(sizeVarp->width())}); + // sizeVarp may live in a base class when the constrained + // array is inherited; route VarRef through its declaring + // class so V3Scope can resolve it. + AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; + sizeVarRefp->classOrPackagep(VN_AS(sizeVarp->user2p(), NodeModule)); + pinp->addPinsp(sizeVarRefp); + randomizep->addStmtsp(pinp->makeStmt()); + } + // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -5520,9 +5580,18 @@ class RandomizeVisitor final : public VNVisitor { } } // For derived classes: clone write_var calls from parent's randomize() + // and save every array that has is size-constrained array to generate + // array element refresh later + std::vector sizeArrayVars; if (nodep->extendsp()) { AstClass* parentClassp = nodep->extendsp()->classp(); while (parentClassp) { + const auto sizeArraysIt = m_sizeConstrainedArrays.find(parentClassp); + if (sizeArraysIt != m_sizeConstrainedArrays.end()) { + for (AstVar* const arrVarp : sizeArraysIt->second) { + sizeArrayVars.push_back(arrVarp); + } + } AstFunc* const parentRandomizep = VN_CAST(m_memberMap.findMember(parentClassp, "randomize"), Func); if (parentRandomizep && parentRandomizep->stmtsp()) { @@ -5620,9 +5689,12 @@ class RandomizeVisitor final : public VNVisitor { solverCallp->add(new AstVarRef{fl, genModp, genp, VAccess::READWRITE}); solverCallp->add(".next(__Vm_rng)"); const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep); - const bool needsSizePhase - = sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty(); - if (needsSizePhase) { + if (sizeArraysIt != m_sizeConstrainedArrays.end()) { + for (AstVar* const arrVarp : sizeArraysIt->second) { + sizeArrayVars.push_back(arrVarp); + } + } + if (!sizeArrayVars.empty()) { AstVar* const sizeOkVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__Vsize_ok", nodep->findBasicDType(VBasicDTypeKwd::BIT)}; sizeOkVarp->funcLocal(true); @@ -5647,39 +5719,8 @@ class RandomizeVisitor final : public VNVisitor { } // Refresh array element tables after resize - for (AstVar* const arrVarp : sizeArraysIt->second) { - // Array elements of class data type are passed to the solver as separate - // variables, so passing the original array variable is redundant, because it - // won't be referenced - const uint32_t unpackedDims = arrVarp->dtypep()->dimensions(false).second; - if (isDynArrOfClassTypeRecurse(arrVarp->dtypep())) { - if (unpackedDims > 1) { - arrVarp->v3warn( - E_UNSUPPORTED, - "Unsupported: Nested array element access in global constraint"); - } - continue; - } - AstCMethodHard* const methodp = new AstCMethodHard{ - fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, - VCMethod::RANDOMIZER_WRITE_VAR}; - methodp->dtypeSetVoid(); - - AstNodeModule* const classp = VN_AS(arrVarp->user2p(), NodeModule); - AstVarRef* const varRefp = new AstVarRef{fl, classp, arrVarp, VAccess::WRITE}; - varRefp->classOrPackagep(classp); - methodp->addPinsp(varRefp); - - const size_t width = arrayElementDTypep(arrVarp->dtypep())->width(); - - methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, width}); - AstNodeExpr* const varnamep = new AstCExpr{ - fl, AstCExpr::Pure{}, "\"" + arrVarp->name() + "\"", arrVarp->width()}; - varnamep->dtypep(arrVarp->dtypep()); - methodp->addPinsp(varnamep); - methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, unpackedDims}); - - randomizep->addStmtsp(methodp->makeStmt()); + for (const auto& arrVarp : sizeArrayVars) { + addArrayElemRefresh(fl, arrVarp, randomizep, genp); } // Rebuild constraints after resize and pin size variables @@ -5687,26 +5728,8 @@ class RandomizeVisitor final : public VNVisitor { AstTaskRef* const setupTaskRefp2 = new AstTaskRef{fl, setupAllTaskp}; randomizep->addStmtsp(setupTaskRefp2->makeStmt()); - for (AstVar* const arrVarp : sizeArraysIt->second) { - AstVar* const sizeVarp = VN_CAST(arrVarp->user4p(), Var); - if (!sizeVarp) continue; - AstCMethodHard* const pinp = new AstCMethodHard{ - fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, - VCMethod::RANDOMIZER_PIN_VAR}; - pinp->dtypeSetVoid(); - AstCExpr* const namep - = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + sizeVarp->name() + "\""}; - namep->dtypeSetUInt32(); - pinp->addPinsp(namep); - pinp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, - static_cast(sizeVarp->width())}); - // sizeVarp may live in a base class when the constrained - // array is inherited; route VarRef through its declaring - // class so V3Scope can resolve it. - AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ}; - sizeVarRefp->classOrPackagep(VN_AS(sizeVarp->user2p(), NodeModule)); - pinp->addPinsp(sizeVarRefp); - randomizep->addStmtsp(pinp->makeStmt()); + for (const auto& arrVarp : sizeArrayVars) { + pinSizeVariable(fl, arrVarp, randomizep, genp); } // Final pass: solve full constraints with sizes pinned diff --git a/test_regress/t/t_constraint_dyn_size_class.v b/test_regress/t/t_constraint_dyn_size_class.v index 377bda47b..52ba79776 100644 --- a/test_regress/t/t_constraint_dyn_size_class.v +++ b/test_regress/t/t_constraint_dyn_size_class.v @@ -10,6 +10,63 @@ `define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%d exp=%d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); `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); +`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(10) 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 + +`define check_rand_foreach(cl, arr, arr_minsize, arr_maxsize, cond) \ +begin \ + automatic int size_count = arr_maxsize - arr_minsize + 1; \ + automatic int max_randomize_count = size_count*20; \ + automatic int loop_count = 0; \ + automatic int occurence_ok = 0; \ + automatic longint size_occurence_count[int]; \ + automatic longint prev_results[arr_maxsize]; \ + automatic int ok[arr_minsize]; \ + if (!bit'(cl.randomize())) $stop; \ + for (int i = 0; i < arr.size(); i++) begin \ + prev_results[i] = longint'(arr[i]); \ + end \ + foreach (arr[i]) begin \ + if (!(cond)) $stop; \ + end \ + while (loop_count < max_randomize_count) begin \ + occurence_ok = 0; \ + if (!bit'(cl.randomize())) $stop; \ + for (int i = 0; i < arr.size(); i++) begin \ + if (longint'(arr[i]) != prev_results[i]) ok[i] = 1; \ + prev_results[i] = longint'(arr[i]); \ + end \ + foreach (arr[i]) begin \ + if (!(cond)) $stop; \ + end \ + size_occurence_count[arr.size()]++; \ + foreach (size_occurence_count[i]) begin \ + if (size_occurence_count[i] >= 3) occurence_ok++; \ + end \ + if (occurence_ok == size_count) break; \ + loop_count++; \ + end \ + if (loop_count >= max_randomize_count) $stop; \ + foreach (ok[i]) begin \ + if (ok[i] != 1) $stop; \ + end \ +end + // verilog_format: on // verilator lint_on @@ -82,16 +139,123 @@ class ExtClass1 extends BaseClass; } endclass +typedef int arr_tdef[]; + +class InnerSizeElem; + rand int arr[]; + rand arr_tdef tdef; + + constraint c { + tdef.size <= 5; + tdef.size >= 3; + + arr.size <= 5; + arr.size >= 3; + + foreach (arr[i]) { + arr[i] >= 'hAAAAAAAA; + arr[i] <= 'hBBBBBBBB; + } + }; +endclass + +class InnerElem; + rand arr_tdef tdef; + rand int arr[]; + + constraint c { + foreach (arr[i]) { + arr[i] >= 'hEEEEEEEE; + arr[i] <= 'hFEEEEEEE; + } + }; +endclass + +class InnerSize; + rand arr_tdef tdef; + rand int arr[]; + + constraint c { + tdef.size <= 6; + tdef.size >= 3; + + arr.size <= 6; + arr.size >= 3; + }; +endclass + +// Class with array-size constraint inside base class +class OuterEmpty extends InnerSizeElem; +endclass + +// Class with array-size constraint inside base class and +// inside the class itself +class OuterElemAndSize extends InnerSizeElem; + rand arr_tdef outer_tdef; + rand int outer_arr[]; + + constraint cc { + outer_tdef.size <= 5; + outer_tdef.size >= 3; + + outer_arr.size <= 5; + outer_arr.size >= 3; + + foreach (outer_arr[i]) { + outer_arr[i] >= 'hABBBBBBB; + outer_arr[i] <= 'hBAAAAAAA; + } + }; +endclass + +class OuterElemInnerSize extends InnerSize; + constraint cc { + foreach(arr[i]) { + arr[i] >= 'h44444444; + arr[i] <= 'h55555555; + } + }; +endclass + +class OuterSizeInnerElem extends InnerElem; + constraint cc { + tdef.size <= 5; + tdef.size >= 3; + + arr.size <= 5; + arr.size >= 3; + }; +endclass + +class OuterSizeInnerSize extends InnerSize; + constraint cc { + tdef.size <= 5; + + arr.size <= 5; + }; +endclass + module t; ExtClass0 ext0; ExtClass1 ext1; IndepClass indep; + OuterEmpty outEmpt; + OuterElemAndSize outElemSize; + OuterElemInnerSize outElemInSize; + OuterSizeInnerElem outSizeInElem; + OuterSizeInnerSize outSizeInSize; initial begin int randomize_result; indep = new; ext0 = new; ext1 = new; + outEmpt = new; + outElemSize = new; + outElemInSize = new; + outSizeInElem = new; + outSizeInSize = new; + repeat (10) begin randomize_result = ext0.randomize(); `checkd(randomize_result, 1); @@ -120,6 +284,43 @@ module t; end end + `check_rand(outEmpt, outEmpt.tdef.size(), + (outEmpt.tdef.size() <= 5 && outEmpt.tdef.size() >= 3)); + `check_rand(outEmpt, outEmpt.arr.size(), + (outEmpt.arr.size() <= 5 && outEmpt.arr.size() >= 3)); + `check_rand_foreach(outEmpt, outEmpt.arr, 3, 5, + (outEmpt.arr[i] >= 'hAAAAAAAA && outEmpt.arr[i] <= 'hBBBBBBBB)); + + `check_rand(outElemSize, outElemSize.outer_tdef.size(), + (outElemSize.outer_tdef.size() <= 5 && outElemSize.outer_tdef.size() >= 3)); + `check_rand(outElemSize, outElemSize.arr.size(), + (outElemSize.arr.size() <= 5 && outElemSize.arr.size() >= 3)); + `check_rand_foreach(outElemSize, outElemSize.arr, 3, 5, + (outElemSize.arr[i] >= 'hAAAAAAAA && outElemSize.arr[i] <= 'hBBBBBBBB)); + `check_rand(outElemSize, outElemSize.outer_arr.size(), + (outElemSize.outer_arr.size() <= 5 && outElemSize.outer_arr.size() >= 3)); + `check_rand_foreach(outElemSize, outElemSize.outer_arr, 3, 5, + (outElemSize.outer_arr[i] >= 'hABBBBBBB && outElemSize.outer_arr[i] <= 'hBAAAAAAA)); + + `check_rand(outElemInSize, outElemInSize.tdef.size(), + (outElemInSize.tdef.size() <= 6 && outElemInSize.tdef.size() >= 3)); + `check_rand(outElemInSize, outElemInSize.arr.size(), + (outElemInSize.arr.size() <= 6 && outElemInSize.arr.size() >= 3)); + `check_rand_foreach(outElemInSize, outElemInSize.arr, 3, 6, + (outElemInSize.arr[i] >= 'h44444444 && outElemInSize.arr[i] <= 'h55555555)); + + `check_rand(outSizeInElem, outSizeInElem.tdef.size(), + (outSizeInElem.tdef.size() <= 5 && outSizeInElem.tdef.size() >= 3)); + `check_rand(outSizeInElem, outSizeInElem.arr.size(), + (outSizeInElem.arr.size() <= 5 && outSizeInElem.arr.size() >= 3)); + `check_rand_foreach(outSizeInElem, outSizeInElem.arr, 3, 5, + (outSizeInElem.arr[i] >= 'hEEEEEEEE && outSizeInElem.arr[i] <= 'hFEEEEEEE)); + + `check_rand(outSizeInSize, outSizeInSize.tdef.size(), + (outSizeInSize.tdef.size() >= 3 && outSizeInSize.tdef.size() <= 5)); + `check_rand(outSizeInSize, outSizeInSize.arr.size(), + (outSizeInSize.arr.size() >= 3 && outSizeInSize.arr.size() <= 5)); + $write("*-* All Finished *-*\n"); $finish(); end