From 6b4183632cef534bd9e6bff7a91104825bf3b98d Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 3 Feb 2025 17:56:00 +0100 Subject: [PATCH] Support Unpacked Structures' Constrained Randomization (#5657) (#5759) --- include/verilated.h | 3 + include/verilated_random.cpp | 2 +- include/verilated_random.h | 35 ++++++++--- src/V3AstNodeDType.h | 17 +++++- src/V3AstNodes.cpp | 14 +++++ src/V3EmitCHeaders.cpp | 39 ++++++++++++ src/V3Randomize.cpp | 25 ++++++++ test_regress/t/t_constraint_struct.py | 21 +++++++ test_regress/t/t_constraint_struct.v | 87 +++++++++++++++++++++++++++ test_regress/t/t_json_only_tag.out | 8 +-- 10 files changed, 235 insertions(+), 16 deletions(-) create mode 100755 test_regress/t/t_constraint_struct.py create mode 100755 test_regress/t/t_constraint_struct.v diff --git a/include/verilated.h b/include/verilated.h index baccd0610..d832eb7ab 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -175,6 +175,9 @@ enum class VerilatedAssertDirectiveType : uint8_t { using VerilatedAssertType_t = std::underlying_type::type; using VerilatedAssertDirectiveType_t = std::underlying_type::type; +// Type trait for custom struct +template +struct VlIsCustomStruct : public std::false_type {}; //============================================================================= // Utility functions diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index a525df5c4..7e048e6c1 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -508,7 +508,7 @@ void VlRandomizer::clear() { m_constraints.clear(); } #ifdef VL_DEBUG void VlRandomizer::dump() const { for (const auto& var : m_vars) { - VL_PRINTF("Variable (%d): %s\n", var.second->width(), var.second->name()); + VL_PRINTF("Variable (%d): %s\n", var.second->width(), var.second->name().c_str()); } for (const std::string& c : m_constraints) VL_PRINTF("Constraint: %s\n", c.c_str()); } diff --git a/include/verilated_random.h b/include/verilated_random.h index 24ba84385..7b1eaabbb 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -54,21 +54,22 @@ public: using ArrayInfoMap = std::map>; class VlRandomVar VL_NOT_FINAL { - const char* const m_name; // Variable name + std::string m_name; // Variable name void* const m_datap; // Reference to variable data const int m_width; // Variable width in bits const int m_dimension; //Variable dimension, default is 0 const std::uint32_t m_randModeIdx; // rand_mode index public: - VlRandomVar(const char* name, int width, void* datap, int dimension, std::uint32_t randModeIdx) + VlRandomVar(const std::string& name, int width, void* datap, int dimension, + std::uint32_t randModeIdx) : m_name{name} , m_datap{datap} , m_width{width} , m_dimension{dimension} , m_randModeIdx{randModeIdx} {} virtual ~VlRandomVar() = default; - const char* name() const { return m_name; } + std::string name() const { return m_name; } int width() const { return m_width; } int dimension() const { return m_dimension; } virtual void* datap(int idx) const { return m_datap; } @@ -99,7 +100,7 @@ public: template class VlRandomArrayVarTemplate final : public VlRandomVar { public: - VlRandomArrayVarTemplate(const char* name, int width, void* datap, int dimension, + VlRandomArrayVarTemplate(const std::string& name, int width, void* datap, int dimension, std::uint32_t randModeIdx) : VlRandomVar{name, width, datap, dimension, randModeIdx} {} void* datap(int idx) const override { @@ -300,8 +301,9 @@ public: } template - void write_var(T& 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()) { if (m_vars.find(name) != m_vars.end()) return; // TODO: make_unique once VlRandomizer is per-instance not per-ref m_vars[name] @@ -341,6 +343,22 @@ public: 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)), + sizeof(std::get(obj.getMembers(obj))) * 8, + (baseName + "." + obj.memberNames()[I]).c_str(), 0), + 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); + } int idx; std::string generateKey(const std::string& name, int idx) { @@ -355,8 +373,9 @@ public: } template - void record_arr_table(T& var, const std::string name, int dimension, - std::vector indices, std::vector idxWidths) { + 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; diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 83b80717f..1cc96fdbe 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -230,6 +230,7 @@ class AstNodeUOrStructDType VL_NOT_FINAL : public AstNodeDType { const int m_uniqueNum; bool m_packed; bool m_isFourstate = false; // V3Width computes + bool m_constrainedRand = false; // True if struct has constraint expression protected: AstNodeUOrStructDType(VNType t, FileLine* fl, VSigning numericUnpack) @@ -244,7 +245,8 @@ protected: , m_name(other.m_name) , m_uniqueNum(uniqueNumInc()) , m_packed(other.m_packed) - , m_isFourstate(other.m_isFourstate) {} + , m_isFourstate(other.m_isFourstate) + , m_constrainedRand(false) {} public: ASTGEN_MEMBERS_AstNodeUOrStructDType; @@ -284,6 +286,8 @@ public: VNumRange declRange() const VL_MT_STABLE { return VNumRange{hi(), lo()}; } AstNodeModule* classOrPackagep() const { return m_classOrPackagep; } void classOrPackagep(AstNodeModule* classpackagep) { m_classOrPackagep = classpackagep; } + bool isConstrainedRand() { return m_constrainedRand; } + void markConstrainedRand(bool flag) { m_constrainedRand = flag; } }; // === Concrete node types ===================================================== @@ -905,12 +909,14 @@ class AstMemberDType final : public AstNodeDType { string m_name; // Name of variable string m_tag; // Holds the string of the verilator tag -- used in XML output. int m_lsb = -1; // Within this level's packed struct, the LSB of the first bit of the member + bool m_constrainedRand = false; // UNSUP: int m_randType; // Randomization type (IEEE) public: AstMemberDType(FileLine* fl, const string& name, VFlagChildDType, AstNodeDType* dtp, AstNode* valuep) : ASTGEN_SUPER_MemberDType(fl) - , m_name{name} { + , m_name{name} + , m_constrainedRand(false) { childDTypep(dtp); // Only for parser this->valuep(valuep); dtypep(nullptr); // V3Width will resolve @@ -918,13 +924,16 @@ public: } AstMemberDType(FileLine* fl, const string& name, AstNodeDType* dtp) : ASTGEN_SUPER_MemberDType(fl) - , m_name{name} { + , m_name{name} + , m_constrainedRand(false) { UASSERT(dtp, "AstMember created with no dtype"); refDTypep(dtp); dtypep(this); widthFromSub(subDTypep()); } ASTGEN_MEMBERS_AstMemberDType; + void dump(std::ostream& str = std::cout) const override; + void dumpJson(std::ostream& str = std::cout) const override; void dumpSmall(std::ostream& str) const override; string name() const override VL_MT_STABLE { return m_name; } // * = Var name bool hasDType() const override VL_MT_SAFE { return true; } @@ -958,6 +967,8 @@ public: v3fatalSrc("call isCompound on subdata type, not reference"); return false; } + bool isConstrainedRand() const { return m_constrainedRand; } + void markConstrainedRand(bool flag) { m_constrainedRand = flag; } }; class AstNBACommitQueueDType final : public AstNodeDType { // @astgen ptr := m_subDTypep : AstNodeDType // Type of the corresponding variable diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index ab9c98fe0..f0ee72c84 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1950,6 +1950,20 @@ void AstJumpLabel::dump(std::ostream& str) const { } void AstJumpLabel::dumpJson(std::ostream& str) const { dumpJsonGen(str); } +void AstMemberDType::dump(std::ostream& str) const { + this->AstNodeDType::dump(str); + if (isConstrainedRand()) str << " [CONSTRAINEDRAND]"; + if (name() != "") str << " name=" << name(); + if (tag() != "") str << " tag=" << tag(); +} + +void AstMemberDType::dumpJson(std::ostream& str) const { + dumpJsonBoolFunc(str, isConstrainedRand); + dumpJsonStrFunc(str, name); + dumpJsonStrFunc(str, tag); + dumpJsonGen(str); +} + void AstMemberDType::dumpSmall(std::ostream& str) const { this->AstNodeDType::dumpSmall(str); str << "member"; diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 9af1c604d..41c471f0a 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -248,7 +248,43 @@ 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 + // - memberIndices: Retrieve member indices + if (sdtypep->isConstrainedRand()) { + 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"); + } + puts("};\n}\n"); + 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"); + } + puts(">{};\n}\n"); + + 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(", "); + } + puts(");\n}\n"); + } putns(sdtypep, "\nbool operator==(const " + EmitCBase::prefixNameProtect(sdtypep) + "& rhs) const {\n"); puts("return "); @@ -280,6 +316,9 @@ class EmitCHeader final : public EmitCConstInit { puts(");\n"); puts("}\n"); puts("};\n"); + puts("template <>\n"); + putns(sdtypep, "struct VlIsCustomStruct<" + EmitCBase::prefixNameProtect(sdtypep) + + "> : public std::true_type {};\n"); } // getfunc: VL_ASSIGNSEL_XX(rbits, obits, off, lhsdata, rhsdata); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 6baa90919..d6b8b67ef 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -629,6 +629,11 @@ class ConstraintExprVisitor final : public VNVisitor { const uint32_t unpackedDimensions = dims.second; dimension = unpackedDimensions; } + if (VN_IS(varp->dtypeSkipRefp(), StructDType) + && !VN_AS(varp->dtypeSkipRefp(), StructDType)->packed()) { + VN_AS(varp->dtypeSkipRefp(), StructDType)->markConstrainedRand(true); + dimension = 1; + } methodp->dtypeSetVoid(); AstClass* const classp = VN_AS(varp->user2p(), Class); AstVarRef* const varRefp @@ -706,6 +711,26 @@ class ConstraintExprVisitor final : public VNVisitor { editSMT(nodep, nodep->fromp(), lsbp, msbp); } + 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()) { + 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}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } void visit(AstAssocSel* nodep) override { if (editFormat(nodep)) return; FileLine* const fl = nodep->fileline(); diff --git a/test_regress/t/t_constraint_struct.py b/test_regress/t/t_constraint_struct.py new file mode 100755 index 000000000..a2b131082 --- /dev/null +++ b/test_regress/t/t_constraint_struct.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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.v b/test_regress/t/t_constraint_struct.v new file mode 100755 index 000000000..4462017bb --- /dev/null +++ b/test_regress/t/t_constraint_struct.v @@ -0,0 +1,87 @@ +// 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 + +typedef struct packed { + bit [7:0] byte_value; + int int_value; +} PackedStruct; + +typedef struct { + rand bit [7:0] byte_value; + rand int int_value; + int non_rand_value; // Non-randomized member +} UnpackedStruct; + +class PackedStructTest; + rand PackedStruct packed_struct; + + function new(); + packed_struct.byte_value = 8'hA0; + packed_struct.int_value = 0; + endfunction + + // Constraint block for packed struct + constraint packed_struct_constraint { + packed_struct.byte_value == 8'hA0; + packed_struct.int_value inside {[0:100]}; + } + + // Self-check function for packed struct + function void check(); + if (packed_struct.byte_value != 8'hA0) $stop; + if (!(packed_struct.int_value inside {[0:100]})) $stop; + endfunction +endclass + +class UnpackedStructTest; + rand UnpackedStruct unpacked_struct; + + function new(); + unpacked_struct.byte_value = 8'h00; + unpacked_struct.int_value = 0; + unpacked_struct.non_rand_value = 42; + endfunction + + // Constraint block for unpacked struct + constraint unpacked_struct_constraint { + unpacked_struct.byte_value inside {8'hA0, 8'hB0, 8'hC0}; + unpacked_struct.int_value inside {[50:150]}; + } + + // Self-check function for unpacked struct + function void check(); + if (!(unpacked_struct.byte_value inside {8'hA0, 8'hB0, 8'hC0})) $stop; + if (!(unpacked_struct.int_value inside {[50:150]})) $stop; + if (unpacked_struct.non_rand_value != 42) $stop; // Check non-randomized member + endfunction +endclass + +module t_constraint_struct; + PackedStructTest packed_struct_test; + UnpackedStructTest unpacked_struct_test; + int success; + + initial begin + // Test packed struct + packed_struct_test = new(); + repeat(10) begin + success = packed_struct_test.randomize(); + if (success == 0) $stop; + packed_struct_test.check(); // Self-check for packed struct + end + + // Test unpacked struct + unpacked_struct_test = new(); + repeat(10) begin + success = unpacked_struct_test.randomize(); + if (success == 0) $stop; + unpacked_struct_test.check(); // Self-check for unpacked struct + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index fe4fb9b92..d0864dfe4 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -69,10 +69,10 @@ {"type":"BASICDTYPE","name":"logic","addr":"(RB)","loc":"d,24:7,24:12","dtypep":"(RB)","keyword":"logic","generic":false,"rangep": []}, {"type":"STRUCTDTYPE","name":"m.my_struct","addr":"(K)","loc":"d,20:12,20:18","dtypep":"(K)","packed":true,"isFourstate":true,"generic":false,"classOrPackagep":"UNLINKED", "membersp": [ - {"type":"MEMBERDTYPE","name":"clk","addr":"(SB)","loc":"d,21:19,21:22","dtypep":"(OB)","generic":false,"childDTypep": [],"valuep": []}, - {"type":"MEMBERDTYPE","name":"k","addr":"(TB)","loc":"d,22:19,22:20","dtypep":"(PB)","generic":false,"childDTypep": [],"valuep": []}, - {"type":"MEMBERDTYPE","name":"enable","addr":"(UB)","loc":"d,23:19,23:25","dtypep":"(QB)","generic":false,"childDTypep": [],"valuep": []}, - {"type":"MEMBERDTYPE","name":"data","addr":"(VB)","loc":"d,24:19,24:23","dtypep":"(RB)","generic":false,"childDTypep": [],"valuep": []} + {"type":"MEMBERDTYPE","name":"clk","addr":"(SB)","loc":"d,21:19,21:22","dtypep":"(OB)","isConstrainedRand":false,"name":"clk","tag":"this is clk","generic":false,"refDTypep":"(OB)","childDTypep": [],"valuep": []}, + {"type":"MEMBERDTYPE","name":"k","addr":"(TB)","loc":"d,22:19,22:20","dtypep":"(PB)","isConstrainedRand":false,"name":"k","tag":"","generic":false,"refDTypep":"(PB)","childDTypep": [],"valuep": []}, + {"type":"MEMBERDTYPE","name":"enable","addr":"(UB)","loc":"d,23:19,23:25","dtypep":"(QB)","isConstrainedRand":false,"name":"enable","tag":"enable","generic":false,"refDTypep":"(QB)","childDTypep": [],"valuep": []}, + {"type":"MEMBERDTYPE","name":"data","addr":"(VB)","loc":"d,24:19,24:23","dtypep":"(RB)","isConstrainedRand":false,"name":"data","tag":"data","generic":false,"refDTypep":"(RB)","childDTypep": [],"valuep": []} ]}, {"type":"IFACEREFDTYPE","name":"","addr":"(O)","loc":"d,29:8,29:12","dtypep":"(O)","isPortDecl":false,"isVirtual":false,"cellName":"itop","ifaceName":"ifc","modportName":"","generic":false,"ifacep":"UNLINKED","cellp":"(L)","modportp":"UNLINKED","paramsp": []}, {"type":"BASICDTYPE","name":"logic","addr":"(S)","loc":"d,31:27,31:28","dtypep":"(S)","keyword":"logic","range":"31:0","generic":true,"rangep": []},