Support constraint random for StructArray (#5805) (#5937)

This commit is contained in:
Yilou Wang 2025-04-16 13:08:46 +02:00 committed by GitHub
parent d0c4cc3938
commit e0fdb933a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 287 additions and 71 deletions

View File

@ -175,9 +175,14 @@ enum class VerilatedAssertDirectiveType : uint8_t {
using VerilatedAssertType_t = std::underlying_type<VerilatedAssertType>::type;
using VerilatedAssertDirectiveType_t = std::underlying_type<VerilatedAssertDirectiveType>::type;
// Type trait for custom struct
// Type trait: whether T is a user-defined custom struct
template <typename>
struct VlIsCustomStruct : public std::false_type {};
// Type trait: used to detect if array element is a custom struct (e.g. for struct arrays)
template <typename T>
struct VlContainsCustomStruct : VlIsCustomStruct<T> {};
//=============================================================================
// Utility functions

View File

@ -200,8 +200,8 @@ class VlRandomizer final {
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
std::map<size_t, std::string> seen_values; // Record String Index to avoid conflicts
const VlQueue<CData>* 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 T_Key>
typename std::enable_if<std::is_integral<T_Key>::value && (sizeof(T_Key) <= 4)>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& 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 T_Key>
typename std::enable_if<std::is_integral<T_Key>::value && (sizeof(T_Key) > 4)>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& 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 T_Key>
typename std::enable_if<VlIsVlWide<T_Key>::value>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& 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 T_Key>
typename std::enable_if<std::is_same<T_Key, std::string>::value>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
@ -289,6 +300,8 @@ public:
idx_width = 128;
}
// process_key: Unsupported key type fallback
template <typename T_Key>
typename std::enable_if<!std::is_integral<T_Key>::value
&& !std::is_same<T_Key, std::string>::value
@ -300,8 +313,13 @@ public:
"supported currently.");
}
// -----------------------------------------
// --- write_var to register variables ---
// -----------------------------------------
// Register scalar variable (non-struct, basic type)
template <typename T>
typename std::enable_if<!VlIsCustomStruct<T>::value, void>::type
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
@ -309,28 +327,60 @@ public:
m_vars[name]
= std::make_shared<const VlRandomVar>(name, width, &var, dimension, randmodeIdx);
}
// Register user-defined struct variable by recursively writing members
template <typename T>
void write_var(VlQueue<T>& var, int width, const char* name, int dimension,
typename std::enable_if<VlIsCustomStruct<T>::value, void>::type
write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
modifyMembers(var, var.memberIndices(), name);
}
// Register queue of non-struct types
template <typename T>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(VlQueue<T>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T>>>(
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 T>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
write_var(VlQueue<T>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (dimension > 0) record_struct_arr(var, name, dimension, {}, {});
}
// Register unpacked array of non-struct types
template <typename T, std::size_t N_Depth>
void write_var(VlUnpacked<T, N_Depth>& var, int width, const char* name, int dimension,
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(VlUnpacked<T, N_Depth>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlUnpacked<T, N_Depth>>>(
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 T, std::size_t N_Depth>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
write_var(VlUnpacked<T, N_Depth>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (dimension > 0) record_struct_arr(var, name, dimension, {}, {});
}
// Register associative array of non-struct types
template <typename T_Key, typename T_Value>
void write_var(VlAssocArray<T_Key, T_Value>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
@ -339,59 +389,28 @@ public:
= std::make_shared<const VlRandomArrayVarTemplate<VlAssocArray<T_Key, T_Value>>>(
name, width, &var, dimension, randmodeIdx);
if (dimension > 0) {
idx = 0;
m_index = 0;
record_arr_table(var, name, dimension, {}, {});
}
}
template <typename T, std::size_t... I>
void modifyMembers(T& obj, std::index_sequence<I...>, std::string baseName) {
// Use the indices to access each member via std::get
(void)std::initializer_list<int>{
(write_var(std::get<I>(obj.getMembers(obj)), obj.memberWidth()[I],
(baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]),
0)...};
}
template <typename T>
typename std::enable_if<VlIsCustomStruct<T>::value, void>::type
write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::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 T>
typename std::enable_if<!std::is_class<T>::value, void>::type
record_arr_table(T& var, const std::string name, int dimension, std::vector<IData> indices,
std::vector<size_t> idxWidths) {
const std::string key = generateKey(name, idx);
m_arr_vars[key] = std::make_shared<ArrayInfo>(name, &var, idx, indices, idxWidths);
++idx;
}
template <typename T>
void record_arr_table(VlQueue<T>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> 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<ArrayInfo>(name, &var, m_index, indices, idxWidths);
++m_index;
}
// Recursively record all elements in an unpacked array
template <typename T, std::size_t N_Depth>
void record_arr_table(VlUnpacked<T, N_Depth>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
@ -406,6 +425,23 @@ public:
}
}
}
// Recursively record all elements in a queue
template <typename T>
void record_arr_table(VlQueue<T>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> 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 <typename T_Key, typename T_Value>
void record_arr_table(VlAssocArray<T_Key, T_Value>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
@ -433,6 +469,76 @@ public:
}
}
// Register a single structArray element via write_var
template <typename T>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
record_struct_arr(T& var, const std::string name, int dimension, std::vector<IData> indices,
std::vector<size_t> idxWidths) {
std::ostringstream oss;
for (size_t i = 0; i < indices.size(); ++i) {
oss << std::hex << static_cast<int>(indices[i]);
if (i < indices.size() - 1) oss << ".";
}
write_var(var, 1ULL, (name + "." + oss.str()).c_str(), 1ULL);
}
// Recursively process VlUnpacked of structs
template <typename T, std::size_t N_Depth>
void record_struct_arr(VlUnpacked<T, N_Depth>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> 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 <typename T>
void record_struct_arr(VlQueue<T>& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> 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 <typename T, std::size_t... I>
void modifyMembers(T& obj, std::index_sequence<I...>, std::string baseName) {
// Use the indices to access each member via std::get
(void)std::initializer_list<int>{
(write_var(std::get<I>(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<CData>& randmode) { m_randmode = &randmode; }

View File

@ -926,6 +926,9 @@ std::string VL_TO_STRING(const VlQueue<T_Value, N_MaxSize>& obj) {
return obj.to_string();
}
template <typename T_Value, size_t N_MaxSize>
struct VlContainsCustomStruct<VlQueue<T_Value, N_MaxSize>> : VlContainsCustomStruct<T_Value> {};
//===================================================================
// 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<T_Key, T_Value>& obj) {
return obj.to_string();
}
template <typename T_Key, typename T_Value>
struct VlContainsCustomStruct<VlAssocArray<T_Key, T_Value>> : VlContainsCustomStruct<T_Key> {};
template <typename T_Key, typename T_Value>
void VL_READMEM_N(bool hex, int bits, const std::string& filename,
VlAssocArray<T_Key, T_Value>& obj, QData start, QData end) VL_MT_SAFE {
@ -1591,6 +1597,9 @@ std::string VL_TO_STRING(const VlUnpacked<T_Value, N_Depth>& obj) {
return obj.to_string();
}
template <typename T, int N>
struct VlContainsCustomStruct<VlUnpacked<T, N>> : VlContainsCustomStruct<T> {};
//===================================================================
// Helper to apply the given indices to a target expression

View File

@ -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();

View File

@ -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<std::string> 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<int> memberDimension(void) const {\n");
emitMemberVector<AttributeType::Dimension>(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())
if (!itemp->isConstrainedRand()) continue;
if (needComma) puts(",\n");
putns(itemp, itemp->dtypep()->cType("", false, false));
if (itemp->nextp() && VN_AS(itemp->nextp(), MemberDType)->isConstrainedRand())
puts(",\n");
needComma = true;
}
puts(">{};\n}\n");
needComma = false;
putns(sdtypep, "\ntemplate <typename T>");
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");
}

View File

@ -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;

View File

@ -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