diff --git a/include/verilated_random.h b/include/verilated_random.h index 7b1eaabbb..05389240f 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -347,9 +347,8 @@ public: void modifyMembers(T& obj, std::index_sequence, std::string baseName) { // Use the indices to access each member via std::get (void)std::initializer_list{ - (write_var(std::get(obj.getMembers(obj)), - sizeof(std::get(obj.getMembers(obj))) * 8, - (baseName + "." + obj.memberNames()[I]).c_str(), 0), + (write_var(std::get(obj.getMembers(obj)), obj.memberWidth()[I], + (baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]), 0)...}; } diff --git a/include/verilated_types.h b/include/verilated_types.h index 3f8b7006d..cd5ea552e 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -954,7 +954,7 @@ public: VlAssocArray& operator=(VlAssocArray&&) = default; bool operator==(const VlAssocArray& rhs) const { return m_map == rhs.m_map; } bool operator!=(const VlAssocArray& rhs) const { return m_map != rhs.m_map; } - + bool operator<(const VlAssocArray& rhs) const { return m_map < rhs.m_map; } // METHODS T_Value& atDefault() { return m_defaultValue; } const T_Value& atDefault() const { return m_defaultValue; } diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 41c471f0a..b4acd5d48 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -240,6 +240,45 @@ class EmitCHeader final : public EmitCConstInit { emitUnpackedUOrSBody(sdtypep); } } + enum class AttributeType { Width, Dimension }; + // Get member attribute based on type + int getNodeAttribute(const AstMemberDType* itemp, AttributeType type) { + const bool isArrayType + = VN_IS(itemp->dtypep(), UnpackArrayDType) || VN_IS(itemp->dtypep(), DynArrayDType) + || VN_IS(itemp->dtypep(), QueueDType) || VN_IS(itemp->dtypep(), AssocArrayDType); + switch (type) { + case AttributeType::Width: { + if (isArrayType) { + // For arrays, get innermost element width + AstNodeDType* dtype = itemp->dtypep(); + while (dtype->subDTypep()) dtype = dtype->subDTypep(); + return dtype->width(); + } + return itemp->width(); + } + case AttributeType::Dimension: { + // Return array dimension or 0 for non-arrays + return isArrayType ? itemp->dtypep()->dimensions(true).second : 0; + } + default: { + return 0; + } + } + } + template + void emitMemberVector(const AstNodeUOrStructDType* sdtypep) { + puts("return {"); + bool needComma = false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->isConstrainedRand()) continue; + // Comma handling: add before element except first + if (needComma) puts(",\n"); + putns(itemp, std::to_string(getNodeAttribute(itemp, T))); + needComma = true; + } + puts("};\n}\n"); + } void emitUnpackedUOrSBody(AstNodeUOrStructDType* sdtypep) { putns(sdtypep, sdtypep->verilogKwd()); // "struct"/"union" puts(" " + EmitCBase::prefixNameProtect(sdtypep) + " {\n"); @@ -252,6 +291,8 @@ class EmitCHeader final : public EmitCConstInit { // - memberNames: Get member names // - getMembers: Access member references // - memberIndices: Retrieve member indices + // - memberWidth: Retrieve member width + // - memberDimension: Retrieve member dimension if (sdtypep->isConstrainedRand()) { putns(sdtypep, "\nstd::vector memberNames(void) const {\n"); puts("return {"); @@ -263,6 +304,12 @@ class EmitCHeader final : public EmitCConstInit { } puts("};\n}\n"); + putns(sdtypep, "\nstd::vector memberWidth(void) const {\n"); + emitMemberVector(sdtypep); + + putns(sdtypep, "\nstd::vector memberDimension(void) const {\n"); + emitMemberVector(sdtypep); + putns(sdtypep, "\nauto memberIndices(void) const {\n"); puts("return std::index_sequence_for<"); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 909cfe726..1f1842e05 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -712,9 +712,12 @@ class ConstraintExprVisitor final : public VNVisitor { } void visit(AstStructSel* nodep) override { if (VN_IS(nodep->fromp()->dtypep()->skipRefp(), StructDType)) { - AstMemberDType* memberp - = VN_AS(nodep->fromp()->dtypep()->skipRefp(), StructDType)->membersp(); - while (memberp->nextp()) { + AstNodeExpr* const fromp = nodep->fromp(); + if (VN_IS(fromp, StructSel)) { + VN_AS(fromp->dtypep()->skipRefp(), StructDType)->markConstrainedRand(true); + } + AstMemberDType* memberp = VN_AS(fromp->dtypep()->skipRefp(), StructDType)->membersp(); + while (memberp) { if (memberp->name() == nodep->name()) { memberp->markConstrainedRand(true); break; diff --git a/test_regress/t/t_constraint_struct_complex.py b/test_regress/t/t_constraint_struct_complex.py new file mode 100755 index 000000000..466368b3d --- /dev/null +++ b/test_regress/t/t_constraint_struct_complex.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. 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-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_struct_complex.v b/test_regress/t/t_constraint_struct_complex.v new file mode 100755 index 000000000..b760bd375 --- /dev/null +++ b/test_regress/t/t_constraint_struct_complex.v @@ -0,0 +1,128 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + +class ArrayStruct; + /* verilator lint_off SIDEEFFECT */ + // Struct with an unpacked array + typedef int arr_3_t[3]; + typedef int arr_4_t[4]; + typedef struct { + rand arr_3_t arr_3; + arr_4_t arr_4; + rand int arr[3]; + } unpacked_struct_t; + + // Struct with a dynamic array + typedef struct { + rand int arr[]; + } dynamic_struct_t; + + // Struct with a queue + typedef struct { + rand int arr[$]; + } queue_struct_t; + + // Struct with an associative array (string as index) + typedef struct { + rand int arr[string]; + } associative_struct_t; + + // Struct with a multi-dimensional array + typedef struct { + rand int arr[2][3]; + } multi_dim_struct_t; + + // Struct with a mix of dynamic and unpacked arrays + typedef struct { + rand int mix_arr[3][]; + } mixed_struct_t; + + rand unpacked_struct_t s1; + rand dynamic_struct_t s2; + rand queue_struct_t s3; + rand associative_struct_t s4; + rand multi_dim_struct_t s5; + rand mixed_struct_t s6; + + constraint c_unpacked { + foreach (s1.arr[i]) s1.arr[i] inside {1, 2, 3, 4}; + foreach (s1.arr_3[i]) s1.arr_3[i] inside {11, 22, 33, 44, 55}; + } + constraint c_dynamic { foreach (s2.arr[i]) s2.arr[i] inside {[10:20]}; } + constraint c_queue { foreach (s3.arr[i]) s3.arr[i] inside {[100:200]}; } + constraint c_assoc { + s4.arr["one"] inside {[10:50]}; + s4.arr["two"] inside {[51:100]}; + s4.arr["three"] inside {[101:150]}; + } + constraint c_multi_dim { foreach (s5.arr[i, j]) s5.arr[i][j] inside {[0:9]}; } + constraint c_mix { + foreach (s6.mix_arr[i, j]) s6.mix_arr[i][j] inside {[50:100]}; + } + + function new(); + s1.arr = '{1, 2, 3}; + s1.arr_3 = '{1, 2, 3}; + s1.arr_4 = '{0, 2, 3, 4}; + s2.arr = new[3]; + foreach(s2.arr[i]) begin + s2.arr[i] = 'h0 + i; + end + s3.arr.push_back(100); + s3.arr.push_back(200); + s3.arr.push_back(300); + s4.arr["one"] = 1000; + s4.arr["two"] = 2000; + s4.arr["three"] = 3000; + s5.arr = '{ '{default:0}, '{default:0} }; + foreach (s6.mix_arr[i]) begin + s6.mix_arr[i] = new[i + 1]; + end + endfunction + + function void print(); + foreach (s1.arr[i]) $display("s1.arr[%0d] = %0d", i, s1.arr[i]); + foreach (s1.arr_3[i]) $display("s1.arr_3[%0d] = %0d", i, s1.arr_3[i]); + foreach (s1.arr_4[i]) $display("s1.arr_4[%0d] = %0d", i, s1.arr_4[i]); + foreach (s2.arr[i]) $display("s2.arr[%0d] = %0d", i, s2.arr[i]); + foreach (s3.arr[i]) $display("s3.arr[%0d] = %0d", i, s3.arr[i]); + foreach (s4.arr[i]) $display("s4.arr[\"%s\"] = %0d", i, s4.arr[i]); + foreach (s5.arr[i, j]) $display("s5.arr[%0d][%0d] = %0d", i, j, s5.arr[i][j]); + foreach (s6.mix_arr[i, j]) $display("s6.mix_arr[%0d][%0d] = %0d", i, j, s6.mix_arr[i][j]); + endfunction + + // Self-test function to verify constraints + function void self_test(); + foreach (s1.arr[i]) if (!(s1.arr[i] inside {1, 2, 3, 4})) $stop; + foreach (s1.arr_3[i]) if (!(s1.arr_3[i] inside {11, 22, 33, 44, 55})) $stop; + // Note: s1.arr_4[0] is not rand + if ((s1.arr_4[0] != 0) || (s1.arr_4[1] != 2) || (s1.arr_4[2] != 3) || (s1.arr_4[3] != 4)) $stop; + foreach (s2.arr[i]) if (!(s2.arr[i] inside {[10:20]})) $stop; + foreach (s3.arr[i]) if (!(s3.arr[i] inside {[100:200]})) $stop; + if (!(s4.arr["one"] inside {[10:50]})) $stop; + if (!(s4.arr["two"] inside {[51:100]})) $stop; + if (!(s4.arr["three"] inside {[101:150]})) $stop; + foreach (s5.arr[i, j]) if (!(s5.arr[i][j] inside {[0:9]})) $stop; + foreach (s6.mix_arr[i]) if (s6.mix_arr[i].size() == 0) $stop; + foreach (s6.mix_arr[i, j]) if (!(s6.mix_arr[i][j] inside {[50:100]})) $stop; + endfunction + /* verilator lint_off SIDEEFFECT */ +endclass + +module t_constraint_struct_complex; + int success; + ArrayStruct asc; + initial begin + asc = new(); + success = asc.randomize(); + if (success != 1) $stop; + asc.self_test(); + // asc.print(); + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule