diff --git a/include/verilated_random.h b/include/verilated_random.h index 730fbf24f..b216960e1 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -492,7 +492,7 @@ public: typename std::enable_if::value, void>::type write_var(T& var, int width, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { - modifyMembers(var, var.memberIndices(), name); + modifyMembers(var, var.memberIndices(), name, randmodeIdx); } // Register queue of non-struct types @@ -517,7 +517,7 @@ public: typename std::enable_if::value, void>::type write_var(VlQueue& var, int width, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { - if (dimension > 0) record_struct_arr(var, name, dimension, {}, {}); + record_struct_arr(var, name, dimension, {}, {}, randmodeIdx); } // Register unpacked array of non-struct types template @@ -543,7 +543,7 @@ public: typename std::enable_if::value, void>::type write_var(VlUnpacked& var, int /*width*/, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { - if (dimension > 0) record_struct_arr(var, name, dimension, {}, {}); + record_struct_arr(var, name, dimension, {}, {}, randmodeIdx); } // Register associative array of non-struct types @@ -571,7 +571,7 @@ public: typename std::enable_if::value, void>::type write_var(VlAssocArray& var, int /*width*/, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { - if (dimension > 0) record_struct_arr(var, name, dimension, {}, {}); + record_struct_arr(var, name, dimension, {}, {}, randmodeIdx); } // --- Record Arrays: flat and struct --- @@ -702,7 +702,8 @@ public: template typename std::enable_if::value, void>::type record_struct_arr(T& var, const std::string& name, int /*dimension*/, - std::vector indices, std::vector idxWidths) { + std::vector indices, std::vector idxWidths, + std::uint32_t randmodeIdx) { std::ostringstream oss; for (size_t i = 0; i < indices.size(); ++i) { oss << std::hex << std::setw(int(idxWidths[i] / 4)) << std::setfill('0') @@ -710,7 +711,8 @@ public: if (i < indices.size() - 1) oss << "."; } write_var(var, 1ULL, - oss.str().length() > 0 ? (name + "." + oss.str()).c_str() : name.c_str(), 1ULL); + oss.str().length() > 0 ? (name + "." + oss.str()).c_str() : name.c_str(), 1ULL, + randmodeIdx); } template @@ -722,13 +724,15 @@ public: // Recursively process VlUnpacked of structs template void record_struct_arr(VlUnpacked& var, const std::string& name, int dimension, - std::vector indices, std::vector idxWidths) { + std::vector indices, std::vector idxWidths, + std::uint32_t randmodeIdx) { if (dimension > 0 && N_Depth != 0) { constexpr size_t idx_width = 1 << VL_CLOG2_CE_Q(VL_CLOG2_CE_Q(N_Depth) + 1); idxWidths.push_back(idx_width); for (size_t i = 0; i < N_Depth; ++i) { indices.push_back(i); - record_struct_arr(var.operator[](i), name, dimension - 1, indices, idxWidths); + record_struct_arr(var.operator[](i), name, dimension - 1, indices, idxWidths, + randmodeIdx); indices.pop_back(); } } @@ -748,12 +752,14 @@ public: // Recursively process VlQueue of structs template void record_struct_arr(VlQueue& var, const std::string& name, int dimension, - std::vector indices, std::vector idxWidths) { + std::vector indices, std::vector idxWidths, + std::uint32_t randmodeIdx) { if ((dimension > 0) && (var.size() != 0)) { idxWidths.push_back(32); for (size_t i = 0; i < var.size(); ++i) { indices.push_back(i); - record_struct_arr(var.atWrite(i), name, dimension - 1, indices, idxWidths); + record_struct_arr(var.atWrite(i), name, dimension - 1, indices, idxWidths, + randmodeIdx); indices.pop_back(); } } @@ -773,7 +779,7 @@ public: template void record_struct_arr(VlAssocArray& var, const std::string& name, int dimension, const std::vector& indices, - const std::vector& idxWidths) { + const std::vector& idxWidths, std::uint32_t randmodeIdx) { if ((dimension > 0) && (!var.empty())) { for (auto it = var.begin(); it != var.end(); ++it) { const T_Key& key = it->first; @@ -791,7 +797,7 @@ public: std::string result = oss.str(); result.insert(result.begin(), int(idx_width / 4) - result.size(), '0'); record_struct_arr(var.atWrite(key), name + "." + result, dimension - 1, indices, - idxWidths); + idxWidths, randmodeIdx); } } } @@ -821,11 +827,13 @@ public: // Helper: Register all members of a user-defined struct template - void modifyMembers(T& obj, std::index_sequence, const std::string& baseName) { + void modifyMembers(T& obj, std::index_sequence, const std::string& baseName, + std::uint32_t randmodeIdx) { // Use the indices to access each member via std::get (void)std::initializer_list{ (write_var(std::get(obj.getMembers(obj)), obj.memberWidth()[I], - (baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]), + (baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I], + randmodeIdx), 0)...}; } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 8ba865d27..ec149a650 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -38,6 +38,7 @@ #include "V3Ast.h" #include "V3Const.h" #include "V3ConstPool.h" +#include "V3Container.h" #include "V3Error.h" #include "V3FileLine.h" #include "V3Global.h" @@ -785,9 +786,10 @@ class ConstraintExprVisitor final : public VNVisitor { std::set& m_writtenVars; // Track which variable paths have write_var generated // (shared across all constraints) std::set m_inlineWrittenVars; // Per-instance tracking for inline constraints - std::set* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints + VInsertionSet* m_sizeConstrainedArraysp = nullptr; // Arrays with size constraints AstNodeExpr* m_conditionp = nullptr; // Condition under which current expression is defined // (nullptr == always defined) + AstNodeFTask* m_prepareConstrainedArraysp = nullptr; // Grow arrays for indexed struct access uint32_t* m_uniqueConstraintId = nullptr; // Current ID of unique call V3UniqueNames& m_uniqueNames; // Unique names of temporaries, and of blocks holding them AstNode* m_firstExpressionInsideIndexp = nullptr; @@ -2405,10 +2407,10 @@ class ConstraintExprVisitor final : public VNVisitor { AstNodeExpr* activep = modeCondp->thenp()->unlinkFrBack(); // 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); - if (idxFmtp) idxFmtp->unlinkFrBack(); - activep - = new AstSFormatF{fl, (*it)->name(), false, AstNode::addNext(activep, idxFmtp)}; + AstNodeExpr* const remainingArgsp = VN_CAST((*it)->exprsp()->nextp(), NodeExpr); + if (remainingArgsp) { remainingArgsp->unlinkFrBackWithNext(); } + activep = new AstSFormatF{fl, (*it)->name(), false, + AstNode::addNext(activep, remainingArgsp)}; } AstCond* const hoistp = new AstCond{fl, modep, activep, getConstFormat(origp)}; hoistp->user1(true); // Mark as formatted @@ -2878,6 +2880,29 @@ class ConstraintExprVisitor final : public VNVisitor { } } AstNodeExpr* const origp = indexIsRand ? nullptr : nodep->cloneTree(false); + const AstVarRef* const baseRefp + = m_structSel ? VN_CAST(nodep->fromp()->baseFromp(true), VarRef) : nullptr; + if (baseRefp && VN_IS(nodep->pinsp(), Const) + && VN_IS(baseRefp->varp()->dtypep()->skipRefp(), DynArrayDType) + && m_prepareConstrainedArraysp) { + AstNodeExpr* const indexp = nodep->pinsp()->cloneTreePure(false); + AstNodeExpr* const arrayp = nodep->fromp()->cloneTreePure(false); + AstCMethodHard* const currentSizep + = new AstCMethodHard{fl, arrayp->cloneTreePure(false), VCMethod::DYN_SIZE}; + currentSizep->dtypeSetInt(); + AstNodeExpr* const inRangeRequiredp = new AstLogAnd{ + fl, + new AstLteS{fl, + new AstConst{fl, AstConst::WidthedValue{}, indexp->width(), 0}, + indexp->cloneTreePure(false)}, + new AstGteS{fl, indexp->cloneTreePure(false), currentSizep}}; + AstCMethodHard* const resizep + = new AstCMethodHard{fl, arrayp, VCMethod::DYN_RESIZE, + new AstAdd{fl, indexp, new AstConst{fl, 1}}}; + resizep->dtypeSetVoid(); + m_prepareConstrainedArraysp->addStmtsp( + new AstIf{fl, inRangeRequiredp, resizep->makeStmt()}); + } AstCMethodHard* const sizep = m_structSel ? new AstCMethodHard{fl, nodep->fromp()->cloneTreePure(false), VCMethod::DYN_SIZE} @@ -3376,7 +3401,8 @@ public: AstVar* randModeVarp, std::set& writtenVars, uint32_t* uniqueConstraintId, V3UniqueNames& uniqueNames, AstNodeFTask* memberselInitTaskp = nullptr, - std::set* sizeConstrainedArraysp = nullptr) + VInsertionSet* sizeConstrainedArraysp = nullptr, + AstNodeFTask* prepareConstrainedArraysp = nullptr) : m_classp{classp} , m_inlineInitTaskp{inlineInitTaskp} , m_memberselInitTaskp{memberselInitTaskp} @@ -3385,6 +3411,7 @@ public: , m_memberMap{memberMap} , m_writtenVars{writtenVars} , m_sizeConstrainedArraysp{sizeConstrainedArraysp} + , m_prepareConstrainedArraysp{prepareConstrainedArraysp} , m_uniqueConstraintId{uniqueConstraintId} , m_uniqueNames{uniqueNames} { // Pre-pass before SMT lowering: extract conditional disable-soft @@ -3702,7 +3729,7 @@ class RandomizeVisitor final : public VNVisitor { std::map m_randcDtypes; // RandC data type deduplication AstConstraint* m_constraintp = nullptr; // Current constraint std::set m_writtenVars; // Track write_var calls per class to avoid duplicates - std::map> m_sizeConstrainedArrays; // Per-class arrays + std::map> m_sizeConstrainedArrays; // Per-class arrays std::map m_staticConstraintModeVars; // Static constraint mode vars per class std::map m_staticRandModeVars; // Static rand mode vars per class @@ -3846,6 +3873,14 @@ class RandomizeVisitor final : public VNVisitor { m_memberMap.insert(classp, setupAllTaskp); return setupAllTaskp; } + AstTask* createPrepareConstrainedArraysTask(AstClass* const classp) { + static const char* const name = "__Vprepare_constrained_arrays"; + AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + return taskp; + } AstTask* getCreateAggrResizeTask(AstClass* const classp) { static constexpr const char* const name = "__Vresize_constrained_arrays"; AstTask* resizeTaskp = VN_AS(m_memberMap.findMember(classp, name), Task); @@ -5351,6 +5386,16 @@ class RandomizeVisitor final : public VNVisitor { if (const AstSel* const selp = VN_CAST(nodep, Sel)) { return newDistGate(selp->fromp(), randModeVarp, fl); } + if (const AstStructSel* const selp = VN_CAST(nodep, StructSel)) { + return newDistGate(selp->fromp(), randModeVarp, fl); + } + if (const AstCMethodHard* const methodp = VN_CAST(nodep, CMethodHard)) { + if (methodp->method() == VCMethod::ARRAY_AT + && VN_IS(methodp->fromp()->dtypep()->skipRefp(), // LCOV_EXCL_BR_LINE + DynArrayDType)) { // Queue form is rejected before dist lowering + return newDistGate(methodp->fromp(), randModeVarp, fl); + } + } // A conditional re-draws through an active selector or the selected arm. if (const AstCond* const condp = VN_CAST(nodep, Cond)) { AstNodeExpr* const thenGatep = newDistGate(condp->thenp(), randModeVarp, fl); @@ -5692,15 +5737,18 @@ class RandomizeVisitor final : public VNVisitor { varnamep->dtypep(arrVarp->dtypep()); methodp->addPinsp(varnamep); methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, unpackedDims}); + const RandomizeMode rmode = {.asUQuad = arrVarp->user1()}; + if (rmode.usesMode) { + methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, rmode.index}); + } randomizep->addStmtsp(methodp->makeStmt()); } - void pinSizeVariable(FileLine* const fl, AstVar* const arrVarp, AstFunc* const randomizep, - AstVar* const genp) { + AstNodeStmt* newPinSizeVariable(FileLine* const fl, AstVar* const arrVarp, + AstVar* const genp) { AstNodeModule* const genModp = VN_AS(genp->user2p(), NodeModule); - AstVar* const sizeVarp = VN_CAST(arrVarp->user4p(), Var); - if (!sizeVarp) return; + AstVar* const sizeVarp = VN_AS(arrVarp->user4p(), Var); AstCMethodHard* const pinp = new AstCMethodHard{fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, VCMethod::RANDOMIZER_PIN_VAR}; @@ -5710,13 +5758,32 @@ class RandomizeVisitor final : public VNVisitor { 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. + // sizeVarp may live in a base class when the constrained array is inherited; route the + // reference 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()); + return pinp->makeStmt(); + } + + void addFrozenSizePin(FileLine* const fl, AstVar* const arrVarp, AstFunc* const randomizep, + AstVar* const genp, AstVar* const randModeVarp) { + const RandomizeMode rmode = {.asUQuad = arrVarp->user1()}; + if (!rmode.usesMode) return; + AstVar* const sizeVarp = VN_AS(arrVarp->user4p(), Var); + AstNodeModule* const arrClassp = VN_AS(arrVarp->user2p(), NodeModule); + AstNodeModule* const sizeClassp = VN_AS(sizeVarp->user2p(), NodeModule); + AstVarRef* const sizeWritep = new AstVarRef{fl, sizeClassp, sizeVarp, VAccess::WRITE}; + const VCMethod sizeMethod = VN_IS(arrVarp->dtypep()->skipRefp(), AssocArrayDType) + ? VCMethod::ASSOC_SIZE + : VCMethod::DYN_SIZE; + AstCMethodHard* const currentSizep = new AstCMethodHard{ + fl, new AstVarRef{fl, arrClassp, arrVarp, VAccess::READ}, sizeMethod}; + currentSizep->dtypep(sizeVarp->dtypep()); + randomizep->addStmtsp(new AstAssign{fl, sizeWritep, currentSizep}); + AstNodeExpr* const modep = newModeBitRead(arrVarp, nullptr, randModeVarp, fl); + randomizep->addStmtsp( + new AstIf{fl, new AstLogNot{fl, modep}, newPinSizeVariable(fl, arrVarp, genp)}); } // VISITORS @@ -5772,6 +5839,7 @@ class RandomizeVisitor final : public VNVisitor { if (genp) { // Phase 1: Process all constraints (create tasks, run ConstraintExprVisitor) // Setup task refs are NOT added to setupAllTaskp here -- done in phase 2 + AstTask* const prepareArraysTaskp = createPrepareConstrainedArraysTask(nodep); nodep->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) { AstTask* taskp = VN_AS(constrp->user2p(), Task); if (!taskp) { @@ -5791,11 +5859,11 @@ class RandomizeVisitor final : public VNVisitor { if (constrp->itemsp()) { lowerDistConstraints(taskp, constrp->itemsp(), randModeVarp); } - std::set& sizeArrays = m_sizeConstrainedArrays[classp]; + VInsertionSet& sizeArrays = m_sizeConstrainedArrays[classp]; ConstraintExprVisitor{ classp, m_memberMap, constrp->itemsp(), nullptr, genp, randModeVarp, m_writtenVars, &m_uniqueConstraintId, - m_uniqueNames, randomizep, &sizeArrays}; + m_uniqueNames, randomizep, &sizeArrays, prepareArraysTaskp}; if (constrp->itemsp()) { taskp->addStmtsp(wrapIfConstraintMode( nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext())); @@ -5836,14 +5904,14 @@ 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; + VInsertionSet 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); + sizeArrayVars.insert(arrVarp); } } AstFunc* const parentRandomizep @@ -5932,7 +6000,25 @@ class RandomizeVisitor final : public VNVisitor { }); } + const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep); + if (sizeArraysIt != m_sizeConstrainedArrays.end()) { + for (AstVar* const arrVarp : sizeArraysIt->second) sizeArrayVars.insert(arrVarp); + } AstTask* setupAllTaskp = getCreateConstraintSetupFunc(nodep); + if (!sizeArrayVars.empty() && prepareArraysTaskp->stmtsp()) { + AstNodeStmt* const preparep = (new AstTaskRef{fl, prepareArraysTaskp})->makeStmt(); + // A size-constrained array always has a preceding write_var registration. + for (AstNode* stmtp = randomizep->stmtsp(); stmtp; // LCOV_EXCL_BR_LINE + stmtp = stmtp->nextp()) { + bool writesArray = false; + stmtp->foreach([&](AstCMethodHard* methodp) { + writesArray |= methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR; + }); + if (!writesArray) continue; + stmtp->addHereThisAsNext(preparep); + break; + } + } AstTaskRef* const setupTaskRefp = new AstTaskRef{fl, setupAllTaskp}; randomizep->addStmtsp(setupTaskRefp->makeStmt()); @@ -5942,12 +6028,6 @@ class RandomizeVisitor final : public VNVisitor { solverCallp->dtypeSetBit(); solverCallp->add(new AstVarRef{fl, genModp, genp, VAccess::READWRITE}); solverCallp->add(".next(__Vm_rng)"); - const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep); - 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)}; @@ -5959,6 +6039,11 @@ class RandomizeVisitor final : public VNVisitor { finalOkVarp->funcLocal(true); randomizep->addStmtsp(finalOkVarp); + // A disabled array keeps its current size. Pin its size proxy before solving so + // an incompatible size constraint fails instead of resizing the frozen array. + for (AstVar* const arrVarp : sizeArrayVars) + addFrozenSizePin(fl, arrVarp, randomizep, genp, randModeVarp); + // First pass: solve size variables (and other constraints) to determine sizes randomizep->addStmtsp( new AstAssign{fl, new AstVarRef{fl, sizeOkVarp, VAccess::WRITE}, solverCallp}); @@ -5982,8 +6067,8 @@ class RandomizeVisitor final : public VNVisitor { AstTaskRef* const setupTaskRefp2 = new AstTaskRef{fl, setupAllTaskp}; randomizep->addStmtsp(setupTaskRefp2->makeStmt()); - for (const auto& arrVarp : sizeArrayVars) { - pinSizeVariable(fl, arrVarp, randomizep, genp); + for (AstVar* const arrVarp : sizeArrayVars) { + randomizep->addStmtsp(newPinSizeVariable(fl, arrVarp, genp)); } // Final pass: solve full constraints with sizes pinned diff --git a/test_regress/t/t_constraint_dist_randmode_dynamic_array.py b/test_regress/t/t_constraint_dist_randmode_dynamic_array.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_randmode_dynamic_array.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_dist_randmode_dynamic_array.v b/test_regress/t/t_constraint_dist_randmode_dynamic_array.v new file mode 100644 index 000000000..d2adecb9e --- /dev/null +++ b/test_regress/t/t_constraint_dist_randmode_dynamic_array.v @@ -0,0 +1,62 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Verilator Authors. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +`define checkd(got, expected) \ + if ((got) != (expected)) $stop + +typedef struct { + rand int value; +} Entry; + +class Collection; + rand Entry entries[]; + + constraint c_size { + entries.size() == 3; + } + + constraint c_values { + foreach (entries[i]) { + entries[i].value[3:0] dist { + 0 :/ 0, + [1:15] :/ 1 + }; + } + } +endclass + +module t; + initial begin + automatic int randomize_result; + + repeat (20) begin + automatic Collection object = new; + object.entries.rand_mode(0); + object.entries = new[3]; + object.entries[0].value = 1; + object.entries[1].value = 2; + object.entries[2].value = 15; + randomize_result = object.randomize(); + `checkd(randomize_result, 1); + `checkd(object.entries.size(), 3); + `checkd(object.entries[0].value, 1); + `checkd(object.entries[1].value, 2); + `checkd(object.entries[2].value, 15); + + object.entries.rand_mode(1); + randomize_result = object.randomize(); + `checkd(randomize_result, 1); + `checkd(object.entries.size(), 3); + foreach (object.entries[i]) begin + `checkd(object.entries[i].value[3:0] != 0, 1); + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_dynarray_struct.py b/test_regress/t/t_constraint_dynarray_struct.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dynarray_struct.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_dynarray_struct.v b/test_regress/t/t_constraint_dynarray_struct.v new file mode 100644 index 000000000..3c728c9ee --- /dev/null +++ b/test_regress/t/t_constraint_dynarray_struct.v @@ -0,0 +1,137 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// 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); +// verilog_format: on + +typedef struct { + rand int value; +} Entry; + +class StructArray; + rand Entry values[]; + int pre_randomize_count; + + constraint c_size { values.size() == 3; } + + constraint c_values { + values[0].value == 0; + values[1].value == 31; + values[2].value == 15; + } + + function void pre_randomize(); + ++pre_randomize_count; + endfunction +endclass + +class InheritedStructArray extends StructArray; + constraint c_inherited_value { values[2].value == 15; } +endclass + +class AssocArrayControl; + rand int values[int]; + + constraint c_size { values.size() == 2; } + + constraint c_value { values[1] == 31; } +endclass + +class OutOfBoundsArray; + rand Entry values[]; + + constraint c_size { values.size() == 1; } + + constraint c_value { values[1].value == 31; } +endclass + +class ScalarArray; + rand int values[]; + + constraint c_size { values.size() == 3; } + + constraint c_values { + values[0] == 0; + values[1] == 31; + values[2] == 15; + } +endclass + +class InheritedScalarArray extends ScalarArray; +endclass + +module t; + initial begin + automatic int randomize_result; + + repeat (20) begin + automatic StructArray struct_array = new; + automatic InheritedStructArray inherited_struct_array = new; + automatic StructArray inline_struct_array = new; + automatic AssocArrayControl assoc_array = new; + automatic InheritedScalarArray inherited_scalar_array = new; + automatic ScalarArray scalar_array = new; + + randomize_result = struct_array.randomize(); + `checkd(randomize_result, 1) + `checkd(struct_array.pre_randomize_count, 1) + `checkd(struct_array.values.size(), 3) + `checkd(struct_array.values[0].value, 0) + `checkd(struct_array.values[1].value, 31) + `checkd(struct_array.values[2].value, 15) + + randomize_result = inherited_struct_array.randomize(); + `checkd(randomize_result, 1) + `checkd(inherited_struct_array.pre_randomize_count, 1) + `checkd(inherited_struct_array.values.size(), 3) + `checkd(inherited_struct_array.values[0].value, 0) + `checkd(inherited_struct_array.values[1].value, 31) + `checkd(inherited_struct_array.values[2].value, 15) + + inline_struct_array.values = new[3]; + randomize_result = inline_struct_array.randomize() with { + values[1].value == 31; + }; + `checkd(randomize_result, 1) + `checkd(inline_struct_array.pre_randomize_count, 1) + `checkd(inline_struct_array.values.size(), 3) + `checkd(inline_struct_array.values[1].value, 31) + + assoc_array.values[1] = 0; + assoc_array.values[2] = 0; + assoc_array.values.rand_mode(1); + randomize_result = assoc_array.randomize(); + `checkd(randomize_result, 1) + `checkd(assoc_array.values.size(), 2) + `checkd(assoc_array.values[1], 31) + + randomize_result = inherited_scalar_array.randomize(); + `checkd(randomize_result, 1) + `checkd(inherited_scalar_array.values.size(), 3) + `checkd(inherited_scalar_array.values[0], 0) + `checkd(inherited_scalar_array.values[1], 31) + `checkd(inherited_scalar_array.values[2], 15) + + randomize_result = scalar_array.randomize(); + `checkd(randomize_result, 1) + `checkd(scalar_array.values.size(), 3) + `checkd(scalar_array.values[0], 0) + `checkd(scalar_array.values[1], 31) + `checkd(scalar_array.values[2], 15) + end + + begin + automatic OutOfBoundsArray out_of_bounds_array = new; + randomize_result = out_of_bounds_array.randomize(); + `checkd(randomize_result, 0) + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule