parent
e0e164cea2
commit
6b4183632c
|
@ -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
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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": []},
|
||||
|
|
Loading…
Reference in New Issue