diff --git a/include/verilated.h b/include/verilated.h index d832eb7ab..a5af6715b 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -175,9 +175,14 @@ enum class VerilatedAssertDirectiveType : uint8_t { using VerilatedAssertType_t = std::underlying_type::type; using VerilatedAssertDirectiveType_t = std::underlying_type::type; -// Type trait for custom struct +// Type trait: whether T is a user-defined custom struct template struct VlIsCustomStruct : public std::false_type {}; + +// Type trait: used to detect if array element is a custom struct (e.g. for struct arrays) +template +struct VlContainsCustomStruct : VlIsCustomStruct {}; + //============================================================================= // Utility functions diff --git a/include/verilated_random.h b/include/verilated_random.h index d74b00191..aae19ddf1 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -200,8 +200,8 @@ class VlRandomizer final { std::map> m_vars; // Solver-dependent // variables ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration - std::map seen_values; // Record String Index to avoid conflicts const VlQueue* m_randmode; // rand_mode state; + int m_index = 0; // Internal counter for key generation // PRIVATE METHODS void randomConstraint(std::ostream& os, VlRNG& rngr, int bits); @@ -216,6 +216,11 @@ public: // Finds the next solution satisfying the constraints bool next(VlRNG& rngr); + // ----------------------------------------------- + // --- Process the key for associative array --- + // ----------------------------------------------- + + // process_key: Handle integral keys (<= 32-bit) template typename std::enable_if::value && (sizeof(T_Key) <= 4)>::type process_key(const T_Key& key, std::string& indexed_name, std::vector& integral_index, @@ -225,6 +230,8 @@ public: = base_name + "[" + std::to_string(integral_index[integral_index.size() - 1]) + "]"; idx_width = sizeof(T_Key) * 8; } + + // process_key: Handle integral keys (> 32-bit), split into 2 x 32-bit segments template typename std::enable_if::value && (sizeof(T_Key) > 4)>::type process_key(const T_Key& key, std::string& indexed_name, std::vector& integral_index, @@ -244,6 +251,8 @@ public: idx_width = sizeof(T_Key) * 8; } + + // process_key: Handle wide keys (VlWide-like), segment is 32-bit per element template typename std::enable_if::value>::type process_key(const T_Key& key, std::string& indexed_name, std::vector& integral_index, @@ -261,6 +270,8 @@ public: indexed_name = base_name + "[" + index_string + "]"; idx_width = key.size() * 32; } + + // process_key: Handle string key, encoded as 128-bit hex template typename std::enable_if::value>::type process_key(const T_Key& key, std::string& indexed_name, std::vector& integral_index, @@ -289,6 +300,8 @@ public: idx_width = 128; } + + // process_key: Unsupported key type fallback template typename std::enable_if::value && !std::is_same::value @@ -300,8 +313,13 @@ public: "supported currently."); } + // ----------------------------------------- + // --- write_var to register variables --- + // ----------------------------------------- + + // Register scalar variable (non-struct, basic type) template - typename std::enable_if::value, void>::type + 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()) { if (m_vars.find(name) != m_vars.end()) return; @@ -309,28 +327,60 @@ public: m_vars[name] = std::make_shared(name, width, &var, dimension, randmodeIdx); } + + // Register user-defined struct variable by recursively writing members template - void write_var(VlQueue& var, int width, const char* name, int dimension, - std::uint32_t randmodeIdx = std::numeric_limits::max()) { + 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); + } + + // Register queue of non-struct types + template + 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 (m_vars.find(name) != m_vars.end()) return; m_vars[name] = std::make_shared>>( name, width, &var, dimension, randmodeIdx); if (dimension > 0) { - idx = 0; + m_index = 0; record_arr_table(var, name, dimension, {}, {}); } } + + // Register queue of structs + template + 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, {}, {}); + } + + // Register unpacked array of non-struct types template - void write_var(VlUnpacked& var, int width, const char* name, int dimension, - std::uint32_t randmodeIdx = std::numeric_limits::max()) { + 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 (m_vars.find(name) != m_vars.end()) return; m_vars[name] = std::make_shared>>( name, width, &var, dimension, randmodeIdx); if (dimension > 0) { - idx = 0; + m_index = 0; record_arr_table(var, name, dimension, {}, {}); } } + + // Register unpacked array of structs + template + 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, {}, {}); + } + + // Register associative array of non-struct types template void write_var(VlAssocArray& var, int width, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { @@ -339,59 +389,28 @@ public: = std::make_shared>>( name, width, &var, dimension, randmodeIdx); if (dimension > 0) { - idx = 0; + m_index = 0; record_arr_table(var, name, dimension, {}, {}); } } - template - 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)), obj.memberWidth()[I], - (baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]), - 0)...}; - } - template - 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); - } + // TODO: Register associative array of structs - int idx; - std::string generateKey(const std::string& name, int idx) { - if (!name.empty() && name[0] == '\\') { - const size_t space_pos = name.find(' '); - return (space_pos != std::string::npos ? name.substr(0, space_pos) : name) - + std::to_string(idx); - } - const size_t bracket_pos = name.find('['); - return (bracket_pos != std::string::npos ? name.substr(0, bracket_pos) : name) - + std::to_string(idx); - } + // ---------------------------------------- + // --- Record Arrays: flat and struct --- + // ---------------------------------------- + // Record a flat (non-class) element into the array variable table template typename std::enable_if::value, void>::type record_arr_table(T& var, const std::string name, int dimension, std::vector indices, std::vector idxWidths) { - const std::string key = generateKey(name, idx); - m_arr_vars[key] = std::make_shared(name, &var, idx, indices, idxWidths); - ++idx; - } - template - void record_arr_table(VlQueue& var, const std::string name, int dimension, - std::vector indices, std::vector idxWidths) { - if ((dimension > 0) && (var.size() != 0)) { - idxWidths.push_back(32); - for (size_t i = 0; i < var.size(); ++i) { - const std::string indexed_name = name + "[" + std::to_string(i) + "]"; - indices.push_back(i); - record_arr_table(var.atWrite(i), indexed_name, dimension - 1, indices, idxWidths); - indices.pop_back(); - } - } + const std::string key = generateKey(name, m_index); + m_arr_vars[key] = std::make_shared(name, &var, m_index, indices, idxWidths); + ++m_index; } + + // Recursively record all elements in an unpacked array template void record_arr_table(VlUnpacked& var, const std::string name, int dimension, std::vector indices, std::vector idxWidths) { @@ -406,6 +425,23 @@ public: } } } + + // Recursively record all elements in a queue + template + void record_arr_table(VlQueue& var, const std::string name, int dimension, + std::vector indices, std::vector idxWidths) { + if ((dimension > 0) && (var.size() != 0)) { + idxWidths.push_back(32); + for (size_t i = 0; i < var.size(); ++i) { + const std::string indexed_name = name + "[" + std::to_string(i) + "]"; + indices.push_back(i); + record_arr_table(var.atWrite(i), indexed_name, dimension - 1, indices, idxWidths); + indices.pop_back(); + } + } + } + + // Recursively record all elements in an associative array template void record_arr_table(VlAssocArray& var, const std::string name, int dimension, std::vector indices, std::vector idxWidths) { @@ -433,6 +469,76 @@ public: } } + // Register a single structArray element via write_var + 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::ostringstream oss; + for (size_t i = 0; i < indices.size(); ++i) { + oss << std::hex << static_cast(indices[i]); + if (i < indices.size() - 1) oss << "."; + } + write_var(var, 1ULL, (name + "." + oss.str()).c_str(), 1ULL); + } + + // Recursively process VlUnpacked of structs + template + void record_struct_arr(VlUnpacked& var, const std::string name, int dimension, + std::vector indices, std::vector idxWidths) { + if (dimension > 0 && N_Depth != 0) { + idxWidths.push_back(32); + for (size_t i = 0; i < N_Depth; ++i) { + indices.push_back(i); + record_struct_arr(var.operator[](i), name, dimension - 1, indices, idxWidths); + indices.pop_back(); + } + } + } + + // Recursively process VlQueue of structs + template + void record_struct_arr(VlQueue& var, const std::string name, int dimension, + std::vector indices, std::vector idxWidths) { + 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); + indices.pop_back(); + } + } + } + + // TODO: Add support for associative arrays of structs + // Recursively process associative arrays of structs + + // -------------------------- + // --- Helper functions --- + // -------------------------- + + // Helper: Register all members of a user-defined struct + template + 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)), obj.memberWidth()[I], + (baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]), + 0)...}; + } + + // Helper: Generate unique variable key from name and index + std::string generateKey(const std::string& name, int idx) { + if (!name.empty() && name[0] == '\\') { + const size_t space_pos = name.find(' '); + return (space_pos != std::string::npos ? name.substr(0, space_pos) : name) + + std::to_string(idx); + } + const size_t bracket_pos = name.find('['); + return (bracket_pos != std::string::npos ? name.substr(0, bracket_pos) : name) + + std::to_string(idx); + } + void hard(std::string&& constraint); void clear(); void set_randmode(const VlQueue& randmode) { m_randmode = &randmode; } diff --git a/include/verilated_types.h b/include/verilated_types.h index 8a1d73df8..783ba5a65 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -926,6 +926,9 @@ std::string VL_TO_STRING(const VlQueue& obj) { return obj.to_string(); } +template +struct VlContainsCustomStruct> : VlContainsCustomStruct {}; + //=================================================================== // Verilog associative array container // There are no multithreaded locks on this; the base variable must @@ -1261,6 +1264,9 @@ std::string VL_TO_STRING(const VlAssocArray& obj) { return obj.to_string(); } +template +struct VlContainsCustomStruct> : VlContainsCustomStruct {}; + template void VL_READMEM_N(bool hex, int bits, const std::string& filename, VlAssocArray& obj, QData start, QData end) VL_MT_SAFE { @@ -1591,6 +1597,9 @@ std::string VL_TO_STRING(const VlUnpacked& obj) { return obj.to_string(); } +template +struct VlContainsCustomStruct> : VlContainsCustomStruct {}; + //=================================================================== // Helper to apply the given indices to a target expression diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 66706636f..f4109cb73 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1927,6 +1927,7 @@ public: } ASTGEN_MEMBERS_AstSFormatF; string name() const override VL_MT_STABLE { return m_text; } + void name(const string& name) override { m_text = name; } int instrCount() const override { return INSTR_COUNT_PLI; } bool sameNode(const AstNode* samep) const override { return text() == VN_DBG_AS(samep, SFormatF)->text(); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 7aedb9855..a8ccf7694 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -287,6 +287,7 @@ class EmitCHeader final : public EmitCConstInit { putns(itemp, itemp->dtypep()->cType(itemp->nameProtect(), false, false)); puts(";\n"); } + // Three helper functions for struct constrained randomization: // - memberNames: Get member names // - getMembers: Access member references @@ -294,13 +295,15 @@ class EmitCHeader final : public EmitCConstInit { // - memberWidth: Retrieve member width // - memberDimension: Retrieve member dimension if (sdtypep->isConstrainedRand()) { + bool needComma = false; putns(sdtypep, "\nstd::vector memberNames(void) const {\n"); puts("return {"); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp->isConstrainedRand()) putns(itemp, "\"" + itemp->shortName() + "\""); - if (itemp->nextp() && VN_AS(itemp->nextp(), MemberDType)->isConstrainedRand()) - puts(",\n"); + if (!itemp->isConstrainedRand()) continue; + if (needComma) puts(",\n"); + putns(itemp, "\"" + itemp->shortName() + "\""); + needComma = true; } puts("};\n}\n"); @@ -310,25 +313,28 @@ class EmitCHeader final : public EmitCConstInit { putns(sdtypep, "\nstd::vector memberDimension(void) const {\n"); emitMemberVector(sdtypep); + needComma = false; putns(sdtypep, "\nauto memberIndices(void) const {\n"); puts("return std::index_sequence_for<"); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp->isConstrainedRand()) - putns(itemp, itemp->dtypep()->cType("", false, false)); - if (itemp->nextp() && VN_AS(itemp->nextp(), MemberDType)->isConstrainedRand()) - puts(",\n"); + if (!itemp->isConstrainedRand()) continue; + if (needComma) puts(",\n"); + putns(itemp, itemp->dtypep()->cType("", false, false)); + needComma = true; } puts(">{};\n}\n"); + needComma = false; putns(sdtypep, "\ntemplate "); putns(sdtypep, "\nauto getMembers(T& obj) {\n"); puts("return std::tie("); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp->isConstrainedRand()) putns(itemp, "obj." + itemp->nameProtect()); - if (itemp->nextp() && VN_AS(itemp->nextp(), MemberDType)->isConstrainedRand()) - puts(", "); + if (!itemp->isConstrainedRand()) continue; + if (needComma) puts(",\n"); + putns(itemp, "obj." + itemp->nameProtect()); + needComma = true; } puts(");\n}\n"); } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 0ddeaadbd..ff7353eb9 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -481,6 +481,8 @@ class ConstraintExprVisitor final : public VNVisitor { AstVar* m_randModeVarp; // Relevant randmode state variable bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND VMemberMap& m_memberMap; // Member names cached for fast lookup + bool m_structSel = false; // Marks when inside structSel + // (used to format "%@.%@" for struct arrays) AstSFormatF* getConstFormat(AstNodeExpr* nodep) { return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false, @@ -537,6 +539,10 @@ class ConstraintExprVisitor final : public VNVisitor { UASSERT_OBJ(!rhsp, nodep, "Missing emitSMT %r for " << rhsp); UASSERT_OBJ(!thsp, nodep, "Missing emitSMT %t for " << thsp); AstSFormatF* const newp = new AstSFormatF{nodep->fileline(), smtExpr, false, argsp}; + if (m_structSel && newp->name() == "(select %@ %@)") { + newp->name("%@.%@"); + newp->exprsp()->nextp()->name("%x"); + } nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } @@ -712,6 +718,7 @@ class ConstraintExprVisitor final : public VNVisitor { editSMT(nodep, nodep->fromp(), lsbp, msbp); } void visit(AstStructSel* nodep) override { + m_structSel = true; if (VN_IS(nodep->fromp()->dtypep()->skipRefp(), StructDType)) { AstNodeExpr* const fromp = nodep->fromp(); if (VN_IS(fromp, StructSel)) { @@ -726,11 +733,34 @@ class ConstraintExprVisitor final : public VNVisitor { memberp = VN_CAST(memberp->nextp(), MemberDType); } } + // Mark Random for structArray + if (VN_IS(nodep->fromp(), ArraySel)) { + AstNodeExpr* const fromp = VN_AS(nodep->fromp(), ArraySel)->fromp(); + AstStructDType* const dtypep + = VN_AS(fromp->dtypep()->skipRefp()->subDTypep()->skipRefp(), StructDType); + dtypep->markConstrainedRand(true); + AstMemberDType* memberp = dtypep->membersp(); + while (memberp) { + if (memberp->name() == nodep->name()) { + memberp->markConstrainedRand(true); + break; + } else + memberp = VN_CAST(memberp->nextp(), MemberDType); + } + } iterateChildren(nodep); if (editFormat(nodep)) return; FileLine* const fl = nodep->fileline(); - AstSFormatF* const newp - = new AstSFormatF{fl, nodep->fromp()->name() + "." + nodep->name(), false, nullptr}; + AstSFormatF* newp = nullptr; + if (VN_AS(nodep->fromp(), SFormatF)->name() == "%@.%@") { + newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false, + VN_AS(nodep->fromp(), SFormatF)->exprsp()->cloneTreePure(true)}; + newp->exprsp()->nextp()->name("%x"); + } else { + newp = new AstSFormatF{fl, nodep->fromp()->name() + "." + nodep->name(), false, + nullptr}; + } + m_structSel = false; nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } @@ -885,9 +915,14 @@ class ConstraintExprVisitor final : public VNVisitor { if (nodep->name() == "at" && nodep->fromp()->user1()) { iterateChildren(nodep); - AstNodeExpr* const argsp - = AstNode::addNext(nodep->fromp()->unlinkFrBack(), nodep->pinsp()->unlinkFrBack()); - AstSFormatF* const newp = new AstSFormatF{fl, "(select %@ %@)", false, argsp}; + AstNodeExpr* pinp = nodep->pinsp()->unlinkFrBack(); + if (VN_IS(pinp, SFormatF) && m_structSel) VN_AS(pinp, SFormatF)->name("%x"); + AstNodeExpr* const argsp = AstNode::addNext(nodep->fromp()->unlinkFrBack(), pinp); + AstSFormatF* newp = nullptr; + if (m_structSel) + newp = new AstSFormatF{fl, "%@.%@", false, argsp}; + else + newp = new AstSFormatF{fl, "(select %@ %@)", false, argsp}; nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); return; diff --git a/test_regress/t/t_constraint_struct_complex.v b/test_regress/t/t_constraint_struct_complex.v index b760bd375..30626219c 100755 --- a/test_regress/t/t_constraint_struct_complex.v +++ b/test_regress/t/t_constraint_struct_complex.v @@ -112,15 +112,69 @@ class ArrayStruct; /* verilator lint_off SIDEEFFECT */ endclass +class StructArray; + /* verilator lint_off WIDTHTRUNC */ + typedef struct { + rand int arr[3]; + rand int a; + rand bit [3:0] b; + bit c; + } struct_t; + rand struct_t s_arr[2]; + + constraint c_structArray_0 { + foreach (s_arr[i]) + foreach (s_arr[i].arr[j]) + s_arr[i].arr[j] inside {[0:9]}; + } + constraint c_structArray_1 { + foreach (s_arr[i]) s_arr[i].a inside {[10:20]}; + } + + function new(); + foreach (s_arr[i]) begin + foreach (s_arr[i].arr[j]) s_arr[i].arr[j] = 'h0 + j; + s_arr[i].a = 'h10 + i; + s_arr[i].b = 'h0 + i; + s_arr[i].c = i; + end + endfunction + + function void print(); + foreach (s_arr[i]) begin + foreach (s_arr[i].arr[j]) $display("s_arr[%0d].arr[%0d] = %0d", i, j, s_arr[i].arr[j]); + $display("s_arr[%0d].a = %0d", i, s_arr[i].a); + $display("s_arr[%0d].b = %0d", i, s_arr[i].b); + $display("s_arr[%0d].c = %0d", i, s_arr[i].c); + end + endfunction + + function void self_test(); + foreach (s_arr[i]) begin + foreach (s_arr[i].arr[j]) if (!(s_arr[i].arr[j] inside {[0:9]})) $stop; + if (!(s_arr[i].a inside {[10:20]})) $stop; + if (!(s_arr[0].c == 0)) $stop; + if (!(s_arr[1].c == 1)) $stop; + end + endfunction + /* verilator lint_off WIDTHTRUNC */ +endclass + module t_constraint_struct_complex; int success; - ArrayStruct asc; + ArrayStruct as_c; + StructArray sa_c; initial begin - asc = new(); - success = asc.randomize(); + as_c = new(); + sa_c = new(); + success = as_c.randomize(); if (success != 1) $stop; - asc.self_test(); - // asc.print(); + as_c.self_test(); + // as_c.print(); + success = sa_c.randomize(); + if (success != 1) $stop; + sa_c.self_test(); + // sa_c.print(); $write("*-* All Finished *-*\n"); $finish; end