Support Unpacked Structures' Constrained Randomization (#5657) (#5759)

This commit is contained in:
Yilou Wang 2025-02-03 17:56:00 +01:00 committed by GitHub
parent e0e164cea2
commit 6b4183632c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 235 additions and 16 deletions

View File

@ -175,6 +175,9 @@ 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
template <typename>
struct VlIsCustomStruct : public std::false_type {};
//=============================================================================
// Utility functions

View File

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

View File

@ -54,21 +54,22 @@ public:
using ArrayInfoMap = std::map<std::string, std::shared_ptr<const ArrayInfo>>;
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 <typename T>
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 <typename T>
void write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
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()) {
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 <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)),
sizeof(std::get<I>(obj.getMembers(obj))) * 8,
(baseName + "." + obj.memberNames()[I]).c_str(), 0),
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);
}
int idx;
std::string generateKey(const std::string& name, int idx) {
@ -355,8 +373,9 @@ public:
}
template <typename T>
void record_arr_table(T& var, const std::string name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": []},