parent
30edb987d2
commit
4befec4463
|
|
@ -744,8 +744,12 @@ bool VlRandomizer::parseSolution(std::iostream& os, bool log) {
|
||||||
const auto it = m_vars.find(name);
|
const auto it = m_vars.find(name);
|
||||||
if (it == m_vars.end()) continue;
|
if (it == m_vars.end()) continue;
|
||||||
const VlRandomVar& varr = *it->second;
|
const VlRandomVar& varr = *it->second;
|
||||||
if (m_randmodep && !varr.randModeIdxNone()) {
|
if (!varr.randModeIdxNone()) {
|
||||||
if (!m_randmodep->at(varr.randModeIdx())) continue;
|
// Static rand vars have their rand_mode in a class-package shared queue,
|
||||||
|
// not the per-instance one.
|
||||||
|
const VlQueue<CData>* const modep
|
||||||
|
= m_staticVars.count(name) ? m_static_randmodep : m_randmodep;
|
||||||
|
if (modep && !modep->at(varr.randModeIdx())) continue;
|
||||||
}
|
}
|
||||||
if (m_disabledVars.count(name)) continue;
|
if (m_disabledVars.count(name)) continue;
|
||||||
if (!indices.empty()) {
|
if (!indices.empty()) {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
|
|
@ -219,6 +220,8 @@ class VlRandomizer VL_NOT_FINAL {
|
||||||
std::vector<std::string> m_unique_arrays;
|
std::vector<std::string> m_unique_arrays;
|
||||||
std::map<std::string, uint32_t> m_unique_array_sizes;
|
std::map<std::string, uint32_t> m_unique_array_sizes;
|
||||||
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
|
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
|
||||||
|
const VlQueue<CData>* m_static_randmodep = nullptr; // Static rand_mode state (shared)
|
||||||
|
std::unordered_set<std::string> m_staticVars; // Names of static rand vars
|
||||||
int m_index = 0; // Internal counter for key generation
|
int m_index = 0; // Internal counter for key generation
|
||||||
std::set<std::string> m_randcVarNames; // Names of randc variables for cyclic tracking
|
std::set<std::string> m_randcVarNames; // Names of randc variables for cyclic tracking
|
||||||
std::map<std::string, std::set<uint64_t>>
|
std::map<std::string, std::set<uint64_t>>
|
||||||
|
|
@ -647,6 +650,10 @@ public:
|
||||||
void solveBefore(const std::string& beforeName,
|
void solveBefore(const std::string& beforeName,
|
||||||
const std::string& afterName); // Register solve-before ordering
|
const std::string& afterName); // Register solve-before ordering
|
||||||
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
|
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
|
||||||
|
// Shared across all instances; consulted instead of m_randmodep for vars marked via
|
||||||
|
// mark_var_static().
|
||||||
|
void set_static_randmode(const VlQueue<CData>& randmode) { m_static_randmodep = &randmode; }
|
||||||
|
void mark_var_static(const char* const name) { m_staticVars.insert(name); }
|
||||||
#ifdef VL_DEBUG
|
#ifdef VL_DEBUG
|
||||||
void dump() const;
|
void dump() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -834,6 +834,8 @@ public:
|
||||||
RANDOMIZER_WRITE_VAR,
|
RANDOMIZER_WRITE_VAR,
|
||||||
RANDOMIZER_SET_VAR_DISABLED,
|
RANDOMIZER_SET_VAR_DISABLED,
|
||||||
RANDOMIZER_CLEAR_VAR_DISABLED,
|
RANDOMIZER_CLEAR_VAR_DISABLED,
|
||||||
|
RANDOMIZER_MARK_VAR_STATIC,
|
||||||
|
RANDOMIZER_SET_STATIC_RANDMODE,
|
||||||
RNG_GET_RANDSTATE,
|
RNG_GET_RANDSTATE,
|
||||||
RNG_SET_RANDSTATE,
|
RNG_SET_RANDSTATE,
|
||||||
SCHED_ANY_TRIGGERED,
|
SCHED_ANY_TRIGGERED,
|
||||||
|
|
@ -985,6 +987,8 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
||||||
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
|
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
|
||||||
{RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false}, \
|
{RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false}, \
|
||||||
{RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false}, \
|
{RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false}, \
|
||||||
|
{RANDOMIZER_MARK_VAR_STATIC, "mark_var_static", false}, \
|
||||||
|
{RANDOMIZER_SET_STATIC_RANDMODE, "set_static_randmode", false}, \
|
||||||
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
|
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
|
||||||
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \
|
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \
|
||||||
{SCHED_ANY_TRIGGERED, "anyTriggered", false}, \
|
{SCHED_ANY_TRIGGERED, "anyTriggered", false}, \
|
||||||
|
|
|
||||||
|
|
@ -469,11 +469,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
} else if (nodep->argsp() && !VN_IS(nodep->backp(), StmtExpr)) {
|
} else if (nodep->argsp() && !VN_IS(nodep->backp(), StmtExpr)) {
|
||||||
nodep->v3error("'rand_mode()' with arguments cannot be called as a function");
|
nodep->v3error("'rand_mode()' with arguments cannot be called as a function");
|
||||||
valid = false;
|
valid = false;
|
||||||
} else if (randModeTarget.receiverp
|
|
||||||
&& randModeTarget.receiverp->lifetime().isStatic()
|
|
||||||
&& randModeTarget.receiverp->isRand()) {
|
|
||||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'rand_mode()' on static variable");
|
|
||||||
valid = false;
|
|
||||||
} else if (randModeTarget.receiverp && randModeTarget.receiverp->isRand()) {
|
} else if (randModeTarget.receiverp && randModeTarget.receiverp->isRand()) {
|
||||||
// Called on a rand member variable
|
// Called on a rand member variable
|
||||||
RandomizeMode randMode = {};
|
RandomizeMode randMode = {};
|
||||||
|
|
@ -483,11 +478,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
// Called on 'this' or a non-rand class instance
|
// Called on 'this' or a non-rand class instance
|
||||||
randModeTarget.classp->foreachMember([&](AstClass*, AstVar* varp) {
|
randModeTarget.classp->foreachMember([&](AstClass*, AstVar* varp) {
|
||||||
if (!varp->isRand()) return;
|
if (!varp->isRand()) return;
|
||||||
if (varp->lifetime().isStatic()) {
|
|
||||||
nodep->v3warn(E_UNSUPPORTED,
|
|
||||||
"Unsupported: 'rand_mode()' on static variable: "
|
|
||||||
<< varp->prettyNameQ());
|
|
||||||
}
|
|
||||||
RandomizeMode randMode = {};
|
RandomizeMode randMode = {};
|
||||||
randMode.usesMode = true;
|
randMode.usesMode = true;
|
||||||
varp->user1(randMode.asInt);
|
varp->user1(randMode.asInt);
|
||||||
|
|
@ -778,6 +768,19 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
std::set<std::string> m_inlineWrittenVars; // Per-instance tracking for inline constraints
|
std::set<std::string> m_inlineWrittenVars; // Per-instance tracking for inline constraints
|
||||||
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
|
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
|
||||||
|
|
||||||
|
// Routes nested sub-objects with static rand vars when the outer class has none.
|
||||||
|
AstVar* findStaticRandModeVarMember(AstClass* classp) const {
|
||||||
|
while (true) {
|
||||||
|
if (AstVar* const varp
|
||||||
|
= VN_CAST(m_memberMap.findMember(classp, "__Vstaticrandmode"), Var)) {
|
||||||
|
return varp;
|
||||||
|
}
|
||||||
|
AstClassExtends* const extendsp = classp->extendsp();
|
||||||
|
if (!extendsp) return nullptr;
|
||||||
|
classp = extendsp->classp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
|
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
|
||||||
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
|
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
|
||||||
const AstNode* fromp = memberSelp->fromp();
|
const AstNode* fromp = memberSelp->fromp();
|
||||||
|
|
@ -1119,9 +1122,19 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
AstNodeExpr* constFormatp
|
AstNodeExpr* constFormatp
|
||||||
= membersel ? getConstFormat(membersel->cloneTree(false)) : getConstFormat(nodep);
|
= membersel ? getConstFormat(membersel->cloneTree(false)) : getConstFormat(nodep);
|
||||||
|
|
||||||
// Build randmode access: for membersel, use member's class randmode if available
|
// Static rand vars route through the var's owning class's static array
|
||||||
|
// (may differ from m_classp when the rand var lives in a sub-object).
|
||||||
AstNodeExpr* randModeAccess;
|
AstNodeExpr* randModeAccess;
|
||||||
if (membersel) {
|
const bool varIsStatic = varp->lifetime().isStatic();
|
||||||
|
AstClass* const varOwningClassp
|
||||||
|
= varIsStatic ? VN_CAST(varp->user2p(), Class) : nullptr;
|
||||||
|
AstVar* const ownerStaticRandModeVarp
|
||||||
|
= varOwningClassp ? findStaticRandModeVarMember(varOwningClassp) : nullptr;
|
||||||
|
if (varIsStatic && ownerStaticRandModeVarp) {
|
||||||
|
randModeAccess = new AstVarRef{
|
||||||
|
varp->fileline(), VN_AS(ownerStaticRandModeVarp->user2p(), NodeModule),
|
||||||
|
ownerStaticRandModeVarp, VAccess::READ};
|
||||||
|
} else if (membersel) {
|
||||||
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
|
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
|
||||||
AstVar* const effectiveRandModeVarp = getRandModeVarFromClass(varClassp);
|
AstVar* const effectiveRandModeVarp = getRandModeVarFromClass(varClassp);
|
||||||
if (effectiveRandModeVarp) {
|
if (effectiveRandModeVarp) {
|
||||||
|
|
@ -1385,6 +1398,19 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
// evaluation), but toggle disabled state so the solver skips
|
// evaluation), but toggle disabled state so the solver skips
|
||||||
// write-back when rand_mode is off.
|
// write-back when rand_mode is off.
|
||||||
initTaskp->addStmtsp(methodp->makeStmt());
|
initTaskp->addStmtsp(methodp->makeStmt());
|
||||||
|
if (varp->lifetime().isStatic() && randMode.usesMode) {
|
||||||
|
AstCMethodHard* const markp = new AstCMethodHard{
|
||||||
|
varp->fileline(),
|
||||||
|
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule),
|
||||||
|
m_genp, VAccess::READWRITE},
|
||||||
|
VCMethod::RANDOMIZER_MARK_VAR_STATIC};
|
||||||
|
AstNodeExpr* const namep = new AstCExpr{varp->fileline(), AstCExpr::Pure{},
|
||||||
|
"\"" + smtName + "\"", varp->width()};
|
||||||
|
namep->dtypep(varp->dtypep());
|
||||||
|
markp->addPinsp(namep);
|
||||||
|
markp->dtypeSetVoid();
|
||||||
|
initTaskp->addStmtsp(markp->makeStmt());
|
||||||
|
}
|
||||||
if (isGlobalConstrained && membersel && randMode.usesMode) {
|
if (isGlobalConstrained && membersel && randMode.usesMode) {
|
||||||
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
|
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
|
||||||
AstVar* const subRandModeVarp = getRandModeVarFromClass(varClassp);
|
AstVar* const subRandModeVarp = getRandModeVarFromClass(varClassp);
|
||||||
|
|
@ -3043,6 +3069,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
std::map<AstClass*, std::set<AstVar*>> m_sizeConstrainedArrays; // Per-class arrays
|
std::map<AstClass*, std::set<AstVar*>> m_sizeConstrainedArrays; // Per-class arrays
|
||||||
std::map<AstClass*, AstVar*>
|
std::map<AstClass*, AstVar*>
|
||||||
m_staticConstraintModeVars; // Static constraint mode vars per class
|
m_staticConstraintModeVars; // Static constraint mode vars per class
|
||||||
|
std::map<AstClass*, AstVar*> m_staticRandModeVars; // Static rand mode vars per class
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
// Check if two nodes are semantically equivalent (not pointer equality):
|
// Check if two nodes are semantically equivalent (not pointer equality):
|
||||||
|
|
@ -3218,6 +3245,22 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
AstVar* getCreateStaticRandModeVar(AstClass* const classp) {
|
||||||
|
if (m_staticRandModeVars.count(classp)) return m_staticRandModeVars[classp];
|
||||||
|
if (AstClassExtends* const extendsp = classp->extendsp()) {
|
||||||
|
return getCreateStaticRandModeVar(extendsp->classp());
|
||||||
|
}
|
||||||
|
AstVar* const staticModeVarp = createStaticModeVar(classp, "__Vstaticrandmode");
|
||||||
|
m_staticRandModeVars[classp] = staticModeVarp;
|
||||||
|
return staticModeVarp;
|
||||||
|
}
|
||||||
|
AstVar* getStaticRandModeVar(AstClass* const classp) {
|
||||||
|
if (m_staticRandModeVars.count(classp)) return m_staticRandModeVars[classp];
|
||||||
|
if (AstClassExtends* const extendsp = classp->extendsp()) {
|
||||||
|
return getStaticRandModeVar(extendsp->classp());
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
AstVar* createModeVar(AstClass* const classp, const char* const name) {
|
AstVar* createModeVar(AstClass* const classp, const char* const name) {
|
||||||
FileLine* const fl = classp->fileline();
|
FileLine* const fl = classp->fileline();
|
||||||
if (!m_dynarrayDtp) {
|
if (!m_dynarrayDtp) {
|
||||||
|
|
@ -3260,10 +3303,25 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
setRandModep->dtypeSetVoid();
|
setRandModep->dtypeSetVoid();
|
||||||
ftaskp->addStmtsp(setRandModep->makeStmt());
|
ftaskp->addStmtsp(setRandModep->makeStmt());
|
||||||
}
|
}
|
||||||
|
static void addSetStaticRandMode(AstNodeFTask* const ftaskp, AstVar* const genp,
|
||||||
|
AstVar* const staticRandModeVarp) {
|
||||||
|
FileLine* const fl = ftaskp->fileline();
|
||||||
|
AstCMethodHard* const setp = new AstCMethodHard{
|
||||||
|
fl, new AstVarRef{fl, VN_AS(genp->user2p(), NodeModule), genp, VAccess::WRITE},
|
||||||
|
VCMethod::RANDOMIZER_SET_STATIC_RANDMODE,
|
||||||
|
new AstVarRef{fl, VN_AS(staticRandModeVarp->user2p(), NodeModule), staticRandModeVarp,
|
||||||
|
VAccess::READ}};
|
||||||
|
setp->dtypeSetVoid();
|
||||||
|
ftaskp->addStmtsp(setp->makeStmt());
|
||||||
|
}
|
||||||
void createRandomizeClassVars(AstNetlist* const netlistp) {
|
void createRandomizeClassVars(AstNetlist* const netlistp) {
|
||||||
netlistp->foreach([this](AstClass* const classp) {
|
// Defer init to one emission per root with max descendant count;
|
||||||
|
// super.new() runs Base ctor before Derived can resize the static array.
|
||||||
|
std::map<AstClass*, uint32_t> rootStaticRandModeCount;
|
||||||
|
netlistp->foreach([this, &rootStaticRandModeCount](AstClass* const classp) {
|
||||||
bool hasConstraints = false;
|
bool hasConstraints = false;
|
||||||
uint32_t randModeCount = 0;
|
uint32_t randModeCount = 0;
|
||||||
|
uint32_t staticRandModeCount = 0;
|
||||||
uint32_t constraintModeCount = 0;
|
uint32_t constraintModeCount = 0;
|
||||||
uint32_t staticConstraintModeCount = 0;
|
uint32_t staticConstraintModeCount = 0;
|
||||||
classp->foreachMember([&](AstClass*, AstNode* memberp) {
|
classp->foreachMember([&](AstClass*, AstNode* memberp) {
|
||||||
|
|
@ -3288,14 +3346,23 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
constraintModeCount = constraintMode.index + 1;
|
constraintModeCount = constraintMode.index + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (VN_IS(memberp, Var)) {
|
} else if (AstVar* const varp = VN_CAST(memberp, Var)) {
|
||||||
RandomizeMode randMode = {.asInt = memberp->user1()};
|
RandomizeMode randMode = {.asInt = memberp->user1()};
|
||||||
if (!randMode.usesMode) return;
|
if (!randMode.usesMode) return;
|
||||||
|
const bool isStaticVar = varp->lifetime().isStatic();
|
||||||
if (randMode.index == 0) {
|
if (randMode.index == 0) {
|
||||||
randMode.index = randModeCount++;
|
if (isStaticVar) {
|
||||||
|
randMode.index = staticRandModeCount++;
|
||||||
|
} else {
|
||||||
|
randMode.index = randModeCount++;
|
||||||
|
}
|
||||||
memberp->user1(randMode.asInt);
|
memberp->user1(randMode.asInt);
|
||||||
} else {
|
} else {
|
||||||
randModeCount = randMode.index + 1;
|
if (isStaticVar) {
|
||||||
|
staticRandModeCount = randMode.index + 1;
|
||||||
|
} else {
|
||||||
|
randModeCount = randMode.index + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -3309,6 +3376,9 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
if (AstVar* const subVarp = VN_CAST(subMemberp, Var)) {
|
if (AstVar* const subVarp = VN_CAST(subMemberp, Var)) {
|
||||||
const RandomizeMode rm = {.asInt = subVarp->user1()};
|
const RandomizeMode rm = {.asInt = subVarp->user1()};
|
||||||
if (!rm.usesMode) return;
|
if (!rm.usesMode) return;
|
||||||
|
// Static rand vars index into their own class's static
|
||||||
|
// rand mode array, not into the outer __Vrandmode.
|
||||||
|
if (subVarp->lifetime().isStatic()) return;
|
||||||
const uint32_t needed = rm.index + 1;
|
const uint32_t needed = rm.index + 1;
|
||||||
if (needed > randModeCount) randModeCount = needed;
|
if (needed > randModeCount) randModeCount = needed;
|
||||||
}
|
}
|
||||||
|
|
@ -3335,7 +3405,20 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstVar* const staticConstraintModeVarp = getCreateStaticConstraintModeVar(classp);
|
AstVar* const staticConstraintModeVarp = getCreateStaticConstraintModeVar(classp);
|
||||||
makeStaticModeInit(staticConstraintModeVarp, classp, staticConstraintModeCount);
|
makeStaticModeInit(staticConstraintModeVarp, classp, staticConstraintModeCount);
|
||||||
}
|
}
|
||||||
|
if (staticRandModeCount > 0) {
|
||||||
|
getCreateStaticRandModeVar(classp);
|
||||||
|
AstClass* rootp = classp;
|
||||||
|
while (AstClassExtends* const ep = rootp->extendsp()) rootp = ep->classp();
|
||||||
|
uint32_t& slot = rootStaticRandModeCount[rootp];
|
||||||
|
if (staticRandModeCount > slot) slot = staticRandModeCount;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
for (const auto& kv : rootStaticRandModeCount) emitRootStaticModeInit(kv.first, kv.second);
|
||||||
|
}
|
||||||
|
void emitRootStaticModeInit(AstClass* const rootp, const uint32_t count) {
|
||||||
|
AstVar* const staticRandModeVarp = m_staticRandModeVars[rootp];
|
||||||
|
UASSERT_OBJ(staticRandModeVarp, rootp, "Root must have a static rand-mode var");
|
||||||
|
makeStaticModeInit(staticRandModeVarp, rootp, count);
|
||||||
}
|
}
|
||||||
void makeModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) {
|
void makeModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) {
|
||||||
AstNodeModule* const modeVarModp = VN_AS(modeVarp->user2p(), NodeModule);
|
AstNodeModule* const modeVarModp = VN_AS(modeVarp->user2p(), NodeModule);
|
||||||
|
|
@ -3423,9 +3506,11 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
new AstAdd{fl, new AstConst{fl, 1}, new AstVarRef{fl, iterVarp, VAccess::READ}}});
|
new AstAdd{fl, new AstConst{fl, 1}, new AstVarRef{fl, iterVarp, VAccess::READ}}});
|
||||||
return new AstBegin{fl, "", stmtsp, true};
|
return new AstBegin{fl, "", stmtsp, true};
|
||||||
}
|
}
|
||||||
static AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) {
|
AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) {
|
||||||
const RandomizeMode rmode = {.asInt = varp->user1()};
|
const RandomizeMode rmode = {.asInt = varp->user1()};
|
||||||
return VN_AS(wrapIfMode(rmode, getRandModeVarFromClass(classp), stmtp), NodeStmt);
|
AstVar* const modeVarp = varp->lifetime().isStatic() ? getStaticRandModeVar(classp)
|
||||||
|
: getRandModeVarFromClass(classp);
|
||||||
|
return VN_AS(wrapIfMode(rmode, modeVarp, stmtp), NodeStmt);
|
||||||
}
|
}
|
||||||
AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) {
|
AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) {
|
||||||
const RandomizeMode rmode = {.asInt = constrp->user1()};
|
const RandomizeMode rmode = {.asInt = constrp->user1()};
|
||||||
|
|
@ -3794,17 +3879,23 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
exprp->v3fatalSrc("Not a MemberSel nor VarRef");
|
exprp->v3fatalSrc("Not a MemberSel nor VarRef");
|
||||||
return nullptr; // LCOV_EXCL_LINE
|
return nullptr; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
AstNodeExpr* makeSiblingRefp(AstNodeExpr* const exprp, AstVar* const varp,
|
// Build a reference to a rand_mode/constraint_mode dyn-array.
|
||||||
const VAccess access) {
|
// Static-mode vars live on the class package; V3Scope resolves the VarRef later.
|
||||||
|
AstNodeExpr* makeModeVarRef(AstNodeExpr* const exprp, AstVar* const modeVarp,
|
||||||
|
const VAccess access) {
|
||||||
|
if (modeVarp->lifetime().isStatic()) {
|
||||||
|
return new AstVarRef{exprp->fileline(), VN_AS(modeVarp->user2p(), NodeModule),
|
||||||
|
modeVarp, access};
|
||||||
|
}
|
||||||
if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) {
|
if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) {
|
||||||
AstMemberSel* const newMemberSelp
|
AstMemberSel* const newMemberSelp = new AstMemberSel{
|
||||||
= new AstMemberSel{exprp->fileline(), memberSelp->fromp()->cloneTree(false), varp};
|
exprp->fileline(), memberSelp->fromp()->cloneTree(false), modeVarp};
|
||||||
// Set access on all VarRef nodes in the cloned subtree
|
|
||||||
newMemberSelp->foreach([access](AstVarRef* varrefp) { varrefp->access(access); });
|
newMemberSelp->foreach([access](AstVarRef* varrefp) { varrefp->access(access); });
|
||||||
return newMemberSelp;
|
return newMemberSelp;
|
||||||
}
|
}
|
||||||
UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef");
|
UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef");
|
||||||
return new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), Class), varp, access};
|
return new AstVarRef{exprp->fileline(), VN_AS(modeVarp->user2p(), Class), modeVarp,
|
||||||
|
access};
|
||||||
}
|
}
|
||||||
// Get or create a size variable for a constrained dynamic/queue/assoc array.
|
// Get or create a size variable for a constrained dynamic/queue/assoc array.
|
||||||
// Returns the size variable. Sets wasCreated=true if a new variable was made.
|
// Returns the size variable. Sets wasCreated=true if a new variable was made.
|
||||||
|
|
@ -3850,14 +3941,14 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
storeStmtspr = AstNode::addNext(
|
storeStmtspr = AstNode::addNext(
|
||||||
storeStmtspr,
|
storeStmtspr,
|
||||||
new AstAssign{fl, new AstVarRef{fl, randModeTmpVarp, VAccess::WRITE},
|
new AstAssign{fl, new AstVarRef{fl, randModeTmpVarp, VAccess::WRITE},
|
||||||
makeSiblingRefp(siblingExprp, randModeVarp, VAccess::READ)});
|
makeModeVarRef(siblingExprp, randModeVarp, VAccess::READ)});
|
||||||
storeStmtspr = AstNode::addNext(
|
storeStmtspr = AstNode::addNext(
|
||||||
storeStmtspr,
|
storeStmtspr,
|
||||||
makeModeSetLoop(fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE),
|
makeModeSetLoop(fl, makeModeVarRef(siblingExprp, randModeVarp, VAccess::WRITE),
|
||||||
new AstConst{fl, 0}, m_ftaskp));
|
new AstConst{fl, 0}, m_ftaskp));
|
||||||
restoreStmtspr = AstNode::addNext(
|
restoreStmtspr = AstNode::addNext(
|
||||||
restoreStmtspr,
|
restoreStmtspr,
|
||||||
new AstAssign{fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE),
|
new AstAssign{fl, makeModeVarRef(siblingExprp, randModeVarp, VAccess::WRITE),
|
||||||
new AstVarRef{fl, randModeTmpVarp, VAccess::READ}});
|
new AstVarRef{fl, randModeTmpVarp, VAccess::READ}});
|
||||||
return randModeTmpVarp;
|
return randModeTmpVarp;
|
||||||
}
|
}
|
||||||
|
|
@ -3980,9 +4071,10 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
// Generate VarRef with classp as module; V3Scope will update varScopep later
|
// Generate VarRef with classp as module; V3Scope will update varScopep later
|
||||||
// when the variable is moved to the class package.
|
// when the variable is moved to the class package.
|
||||||
if (modeVarp->lifetime().isStatic()) {
|
if (modeVarp->lifetime().isStatic()) {
|
||||||
// Static mode var - generate VarRef that will be resolved by V3Scope
|
// Hint owning class so V3Scope resolves from derived call sites.
|
||||||
if (fromp) VL_DO_DANGLING(fromp->unlinkFrBack()->deleteTree(), fromp);
|
if (fromp) VL_DO_DANGLING(fromp->unlinkFrBack()->deleteTree(), fromp);
|
||||||
return new AstVarRef{fl, classp, modeVarp, VAccess::WRITE};
|
return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp,
|
||||||
|
VAccess::WRITE};
|
||||||
} else if (classp == m_modp) {
|
} else if (classp == m_modp) {
|
||||||
// Called on 'this' or a member of 'this'
|
// Called on 'this' or a member of 'this'
|
||||||
return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp,
|
return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp,
|
||||||
|
|
@ -3996,10 +4088,16 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
// Replace the node with an assignment to the mode variable. Called by visit(AstNodeFTaskRef*)
|
// Replace the node with an assignment to the mode variable. Called by visit(AstNodeFTaskRef*)
|
||||||
void replaceWithModeAssign(AstNodeFTaskRef* const ftaskRefp, AstNode* const receiverp,
|
void replaceWithModeAssign(AstNodeFTaskRef* const ftaskRefp, AstNode* const receiverp,
|
||||||
AstNodeExpr* const lhsp) {
|
AstNodeExpr* const lhsp) {
|
||||||
|
replaceWithModeAssignAndAppend(ftaskRefp, receiverp, lhsp, nullptr);
|
||||||
|
}
|
||||||
|
// Append BEFORE swap; backp()/nextp() unreliable after replaceWith.
|
||||||
|
void replaceWithModeAssignAndAppend(AstNodeFTaskRef* const ftaskRefp, AstNode* const receiverp,
|
||||||
|
AstNodeExpr* const lhsp, AstNode* const appendStmtp) {
|
||||||
FileLine* const fl = ftaskRefp->fileline();
|
FileLine* const fl = ftaskRefp->fileline();
|
||||||
if (ftaskRefp->argsp()) {
|
if (ftaskRefp->argsp()) {
|
||||||
UASSERT_OBJ(VN_IS(ftaskRefp->backp(), StmtExpr), ftaskRefp, "Should be a statement");
|
UASSERT_OBJ(VN_IS(ftaskRefp->backp(), StmtExpr), ftaskRefp, "Should be a statement");
|
||||||
AstNodeExpr* const rhsp = ftaskRefp->argsp()->exprp()->unlinkFrBack();
|
AstNodeExpr* const rhsp = ftaskRefp->argsp()->exprp()->unlinkFrBack();
|
||||||
|
AstNode* newStmtp = nullptr;
|
||||||
if (receiverp) {
|
if (receiverp) {
|
||||||
// Called on a rand member variable/constraint. Set the variable/constraint's
|
// Called on a rand member variable/constraint. Set the variable/constraint's
|
||||||
// mode
|
// mode
|
||||||
|
|
@ -4008,16 +4106,19 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE,
|
AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE,
|
||||||
new AstConst{fl, rmode.index}};
|
new AstConst{fl, rmode.index}};
|
||||||
setp->dtypeSetUInt32();
|
setp->dtypeSetUInt32();
|
||||||
m_stmtp->replaceWith(new AstAssign{fl, setp, rhsp});
|
newStmtp = new AstAssign{fl, setp, rhsp};
|
||||||
} else {
|
} else {
|
||||||
// For rand_mode: Called on 'this' or a non-rand class instance.
|
// For rand_mode: Called on 'this' or a non-rand class instance.
|
||||||
// For constraint_mode: Called on a class instance.
|
// For constraint_mode: Called on a class instance.
|
||||||
// Set the rand mode of all members
|
// Set the rand mode of all members
|
||||||
m_stmtp->replaceWith(makeModeSetLoop(fl, lhsp, rhsp, m_ftaskp));
|
newStmtp = makeModeSetLoop(fl, lhsp, rhsp, m_ftaskp);
|
||||||
}
|
}
|
||||||
|
if (appendStmtp) newStmtp->addNext(appendStmtp);
|
||||||
|
m_stmtp->replaceWith(newStmtp);
|
||||||
pushDeletep(m_stmtp);
|
pushDeletep(m_stmtp);
|
||||||
} else {
|
} else {
|
||||||
UASSERT_OBJ(receiverp, ftaskRefp, "Should have receiver");
|
UASSERT_OBJ(receiverp, ftaskRefp, "Should have receiver");
|
||||||
|
UASSERT_OBJ(!appendStmtp, ftaskRefp, "Append path requires arg-form rand_mode");
|
||||||
const RandomizeMode rmode = {.asInt = receiverp->user1()};
|
const RandomizeMode rmode = {.asInt = receiverp->user1()};
|
||||||
UASSERT_OBJ(rmode.usesMode, ftaskRefp, "Failed to set usesMode");
|
UASSERT_OBJ(rmode.usesMode, ftaskRefp, "Failed to set usesMode");
|
||||||
AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE,
|
AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE,
|
||||||
|
|
@ -4106,7 +4207,11 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
if (commonPrefixp == exprp) break;
|
if (commonPrefixp == exprp) break;
|
||||||
AstVar* const randVarp = getVarFromRef(exprp);
|
AstVar* const randVarp = getVarFromRef(exprp);
|
||||||
AstClass* const classp = VN_AS(randVarp->user2p(), Class);
|
AstClass* const classp = VN_AS(randVarp->user2p(), Class);
|
||||||
AstVar* const randModeVarp = getRandModeVarFromClass(classp);
|
AstVar* const randModeVarp = randVarp->lifetime().isStatic()
|
||||||
|
? getStaticRandModeVar(classp)
|
||||||
|
: getRandModeVarFromClass(classp);
|
||||||
|
UASSERT_OBJ(randModeVarp, randVarp,
|
||||||
|
"Rand var with rand_mode must have a mode array");
|
||||||
if (savedRandModeVarps.find(randModeVarp) == savedRandModeVarps.end()) {
|
if (savedRandModeVarps.find(randModeVarp) == savedRandModeVarps.end()) {
|
||||||
AstVar* const randModeTmpVarp
|
AstVar* const randModeTmpVarp
|
||||||
= makeTmpRandModeVar(exprp, randModeVarp, storeStmtsp, restoreStmtsp);
|
= makeTmpRandModeVar(exprp, randModeVarp, storeStmtsp, restoreStmtsp);
|
||||||
|
|
@ -4115,7 +4220,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
const RandomizeMode randMode = {.asInt = randVarp->user1()};
|
const RandomizeMode randMode = {.asInt = randVarp->user1()};
|
||||||
AstCMethodHard* setp = new AstCMethodHard{
|
AstCMethodHard* setp = new AstCMethodHard{
|
||||||
fl, makeSiblingRefp(exprp, randModeVarp, VAccess::WRITE),
|
fl, makeModeVarRef(exprp, randModeVarp, VAccess::WRITE),
|
||||||
VCMethod::ARRAY_AT_WRITE, new AstConst{fl, randMode.index}};
|
VCMethod::ARRAY_AT_WRITE, new AstConst{fl, randMode.index}};
|
||||||
setp->dtypeSetUInt32();
|
setp->dtypeSetUInt32();
|
||||||
setStmtsp
|
setStmtsp
|
||||||
|
|
@ -4737,6 +4842,12 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
UASSERT_OBJ(newp, randModeClassp, "No new() in class");
|
UASSERT_OBJ(newp, randModeClassp, "No new() in class");
|
||||||
addSetRandMode(newp, genp, randModeVarp);
|
addSetRandMode(newp, genp, randModeVarp);
|
||||||
}
|
}
|
||||||
|
if (AstVar* const staticRandModeVarp = getStaticRandModeVar(nodep)) {
|
||||||
|
// Wire the shared static rand_mode queue into the class generator.
|
||||||
|
AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(nodep, "new"), NodeFTask);
|
||||||
|
UASSERT_OBJ(newp, nodep, "No new() in class");
|
||||||
|
addSetStaticRandMode(newp, genp, staticRandModeVarp);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
|
beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
|
||||||
}
|
}
|
||||||
|
|
@ -4842,13 +4953,36 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
UASSERT_OBJ(randModeTarget.classp, nodep,
|
UASSERT_OBJ(randModeTarget.classp, nodep,
|
||||||
"Should have checked in RandomizeMarkVisitor");
|
"Should have checked in RandomizeMarkVisitor");
|
||||||
AstVar* const receiverp = randModeTarget.receiverp;
|
AstVar* const receiverp = randModeTarget.receiverp;
|
||||||
AstVar* const randModeVarp = getRandModeVarFromClass(randModeTarget.classp);
|
const bool isClassLevel = !(receiverp && receiverp->rand().isRand());
|
||||||
|
// Class-level rand_mode(N) must also flush the shared static array.
|
||||||
|
AstNode* classLevelStaticLoopp = nullptr;
|
||||||
|
if (isClassLevel && nodep->argsp()) {
|
||||||
|
if (AstVar* const sVarp = getStaticRandModeVar(randModeTarget.classp)) {
|
||||||
|
FileLine* const fl = nodep->fileline();
|
||||||
|
AstNodeExpr* const staticLhsp
|
||||||
|
= makeModeAssignLhs(fl, randModeTarget.classp, nullptr, sVarp);
|
||||||
|
AstNodeExpr* const argClonep = nodep->argsp()->exprp()->cloneTreePure(false);
|
||||||
|
classLevelStaticLoopp = makeModeSetLoop(fl, staticLhsp, argClonep, m_ftaskp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool receiverIsStaticRand
|
||||||
|
= receiverp && receiverp->rand().isRand() && receiverp->lifetime().isStatic();
|
||||||
|
AstVar* const randModeVarp = receiverIsStaticRand
|
||||||
|
? getStaticRandModeVar(randModeTarget.classp)
|
||||||
|
: getRandModeVarFromClass(randModeTarget.classp);
|
||||||
|
if (!randModeVarp) {
|
||||||
|
UASSERT_OBJ(isClassLevel && classLevelStaticLoopp, nodep,
|
||||||
|
"Per-instance rand_mode var missing without static fallback");
|
||||||
|
UASSERT_OBJ(VN_IS(nodep->backp(), StmtExpr), nodep, "Should be a statement");
|
||||||
|
m_stmtp->replaceWith(classLevelStaticLoopp);
|
||||||
|
pushDeletep(m_stmtp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
AstNodeExpr* const lhsp = makeModeAssignLhs(nodep->fileline(), randModeTarget.classp,
|
AstNodeExpr* const lhsp = makeModeAssignLhs(nodep->fileline(), randModeTarget.classp,
|
||||||
randModeTarget.fromp, randModeVarp);
|
randModeTarget.fromp, randModeVarp);
|
||||||
replaceWithModeAssign(nodep,
|
replaceWithModeAssignAndAppend(
|
||||||
// If the receiver is not rand, set the rand_mode for all members
|
nodep, receiverp && receiverp->rand().isRand() ? receiverp : nullptr, lhsp,
|
||||||
receiverp && receiverp->rand().isRand() ? receiverp : nullptr,
|
classLevelStaticLoopp);
|
||||||
lhsp);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5092,6 +5226,11 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
// Set rand mode if present (not needed if classGenp exists and was copied)
|
// Set rand mode if present (not needed if classGenp exists and was copied)
|
||||||
AstVar* const randModeVarp = getRandModeVarFromClass(classp);
|
AstVar* const randModeVarp = getRandModeVarFromClass(classp);
|
||||||
if (!classGenp && randModeVarp) addSetRandMode(randomizeFuncp, localGenp, randModeVarp);
|
if (!classGenp && randModeVarp) addSetRandMode(randomizeFuncp, localGenp, randModeVarp);
|
||||||
|
if (!classGenp) {
|
||||||
|
if (AstVar* const sVarp = getStaticRandModeVar(classp)) {
|
||||||
|
addSetStaticRandMode(randomizeFuncp, localGenp, sVarp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate constraint setup code and a hardcoded call to the solver
|
// Generate constraint setup code and a hardcoded call to the solver
|
||||||
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# 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-FileCopyrightText: 2026 Wilson Snyder
|
||||||
|
# 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,296 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2026 PlanV GmbH
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
// verilog_format: off
|
||||||
|
`define stop $stop
|
||||||
|
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||||
|
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
|
||||||
|
// verilog_format: on
|
||||||
|
|
||||||
|
// Tests rand_mode() support for `static rand` class members per IEEE 1800-2023
|
||||||
|
// Section 18.5.10 and Section 18.4. Static rand_mode state is shared across all
|
||||||
|
// instances of a class.
|
||||||
|
|
||||||
|
class Simple;
|
||||||
|
static rand int sx;
|
||||||
|
rand int dy;
|
||||||
|
constraint c {
|
||||||
|
sx > 0;
|
||||||
|
sx < 12;
|
||||||
|
dy > 100;
|
||||||
|
dy < 200;
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Base;
|
||||||
|
static rand bit [3:0] base_sx;
|
||||||
|
rand bit [3:0] base_dy;
|
||||||
|
constraint base_c {
|
||||||
|
base_sx > 0;
|
||||||
|
base_dy > 0;
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Derived extends Base;
|
||||||
|
rand bit [3:0] der_dy;
|
||||||
|
constraint der_c {der_dy > 0;}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// Class with ONLY static rand members; exercises class-level rand_mode() with
|
||||||
|
// no per-instance mode array.
|
||||||
|
class StaticOnly;
|
||||||
|
static rand bit [3:0] sa;
|
||||||
|
static rand bit [3:0] sb;
|
||||||
|
constraint c {
|
||||||
|
sa > 0;
|
||||||
|
sb > 0;
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// Base with two static rand vars exercises non-zero index in the shared static array.
|
||||||
|
class BaseTwo;
|
||||||
|
static rand bit [3:0] base2_a;
|
||||||
|
static rand bit [3:0] base2_b;
|
||||||
|
rand bit [3:0] base2_dy;
|
||||||
|
constraint c {
|
||||||
|
base2_a > 0;
|
||||||
|
base2_b > 0;
|
||||||
|
base2_dy > 0;
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DerivedTwo extends BaseTwo;
|
||||||
|
rand bit [3:0] der2_dy;
|
||||||
|
constraint dc {der2_dy > 0;}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// No constraint blocks: inline randomize-with must still flush static rand_mode.
|
||||||
|
class StaticNoConstraint;
|
||||||
|
static rand bit [3:0] snc_s;
|
||||||
|
rand bit [3:0] snc_d;
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// Base + Derived each declare a static rand var; per-root max-count init must size for both.
|
||||||
|
class BaseS;
|
||||||
|
static rand bit [3:0] base_s;
|
||||||
|
constraint c {base_s > 0;}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DerivedS extends BaseS;
|
||||||
|
static rand bit [3:0] der_s;
|
||||||
|
constraint c2 {der_s > 0;}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t;
|
||||||
|
Simple s1, s2;
|
||||||
|
Derived d1, d2;
|
||||||
|
StaticOnly so1, so2;
|
||||||
|
BaseS bs1;
|
||||||
|
DerivedS ds1, ds2;
|
||||||
|
DerivedTwo dt1;
|
||||||
|
StaticNoConstraint snc1;
|
||||||
|
int saved_sx, saved_dy, rok;
|
||||||
|
bit [3:0] saved_base_sx, saved_base_s, saved_der_s, saved_base2_b;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
s1 = new;
|
||||||
|
s2 = new;
|
||||||
|
|
||||||
|
// ---- Test 1: getter on static rand var (initial state is enabled)
|
||||||
|
`checkd(s1.sx.rand_mode(), 1);
|
||||||
|
`checkd(s2.sx.rand_mode(), 1);
|
||||||
|
|
||||||
|
// ---- Test 2: randomize() with all rand_modes enabled satisfies constraints
|
||||||
|
repeat (20) begin
|
||||||
|
rok = s1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`check_range(Simple::sx, 1, 11);
|
||||||
|
`check_range(s1.dy, 101, 199);
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- Test 3: per-member set on a static rand var is shared across instances
|
||||||
|
s1.sx.rand_mode(0);
|
||||||
|
`checkd(s1.sx.rand_mode(), 0);
|
||||||
|
`checkd(s2.sx.rand_mode(), 0);
|
||||||
|
|
||||||
|
// Non-static dy stays independent on each instance
|
||||||
|
`checkd(s1.dy.rand_mode(), 1);
|
||||||
|
`checkd(s2.dy.rand_mode(), 1);
|
||||||
|
|
||||||
|
// ---- Test 4: solver respects rand_mode(0) on static rand var.
|
||||||
|
// sx is currently a valid value from Test 2; with rand_mode disabled it
|
||||||
|
// must stay at that value across multiple randomize() calls.
|
||||||
|
saved_sx = Simple::sx;
|
||||||
|
repeat (20) begin
|
||||||
|
rok = s1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`checkd(Simple::sx, saved_sx);
|
||||||
|
`check_range(s1.dy, 101, 199);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Re-enable rand_mode for sx via the OTHER instance (sharing test).
|
||||||
|
s2.sx.rand_mode(1);
|
||||||
|
`checkd(s1.sx.rand_mode(), 1);
|
||||||
|
|
||||||
|
repeat (20) begin
|
||||||
|
rok = s1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`check_range(Simple::sx, 1, 11);
|
||||||
|
`check_range(s1.dy, 101, 199);
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- Test 5: class-level obj.rand_mode(0) flushes both per-instance
|
||||||
|
// and static arrays. Disable everything on s1.
|
||||||
|
s1.rand_mode(0);
|
||||||
|
`checkd(s1.sx.rand_mode(), 0);
|
||||||
|
`checkd(s1.dy.rand_mode(), 0);
|
||||||
|
// Static sx is shared, so s2 sees it disabled too.
|
||||||
|
`checkd(s2.sx.rand_mode(), 0);
|
||||||
|
// Non-static dy on s2 is independent of s1's class-level call.
|
||||||
|
`checkd(s2.dy.rand_mode(), 1);
|
||||||
|
|
||||||
|
// Re-randomize s1 with everything off - both fields unchanged.
|
||||||
|
saved_sx = Simple::sx;
|
||||||
|
saved_dy = s1.dy;
|
||||||
|
repeat (10) begin
|
||||||
|
rok = s1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`checkd(Simple::sx, saved_sx);
|
||||||
|
`checkd(s1.dy, saved_dy);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Class-level enable resets both arrays to 1.
|
||||||
|
s1.rand_mode(1);
|
||||||
|
`checkd(s1.sx.rand_mode(), 1);
|
||||||
|
`checkd(s1.dy.rand_mode(), 1);
|
||||||
|
`checkd(s2.sx.rand_mode(), 1);
|
||||||
|
|
||||||
|
// ---- Test 6: inheritance - static rand var declared in base class,
|
||||||
|
// accessed via a derived-class instance.
|
||||||
|
d1 = new;
|
||||||
|
d2 = new;
|
||||||
|
|
||||||
|
`checkd(d1.base_sx.rand_mode(), 1);
|
||||||
|
|
||||||
|
// Randomize first so base_sx satisfies its constraint, then disable it.
|
||||||
|
rok = d1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
if (Base::base_sx == 0) $stop;
|
||||||
|
|
||||||
|
d1.base_sx.rand_mode(0);
|
||||||
|
`checkd(d1.base_sx.rand_mode(), 0);
|
||||||
|
`checkd(d2.base_sx.rand_mode(), 0); // Shared via base class
|
||||||
|
|
||||||
|
// Derived class member dy is non-static, independent.
|
||||||
|
`checkd(d1.der_dy.rand_mode(), 1);
|
||||||
|
`checkd(d2.der_dy.rand_mode(), 1);
|
||||||
|
|
||||||
|
saved_base_sx = Base::base_sx;
|
||||||
|
repeat (20) begin
|
||||||
|
rok = d1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`checkd(Base::base_sx, saved_base_sx); // disabled - unchanged
|
||||||
|
if (d1.der_dy == 0) $stop;
|
||||||
|
if (d1.base_dy == 0) $stop;
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- Test 7: class-level rand_mode(N) on a class with ONLY static rand members.
|
||||||
|
so1 = new;
|
||||||
|
so2 = new;
|
||||||
|
`checkd(so1.sa.rand_mode(), 1);
|
||||||
|
`checkd(so1.sb.rand_mode(), 1);
|
||||||
|
so1.rand_mode(0); // must not crash
|
||||||
|
`checkd(so1.sa.rand_mode(), 0);
|
||||||
|
`checkd(so1.sb.rand_mode(), 0);
|
||||||
|
`checkd(so2.sa.rand_mode(), 0); // shared
|
||||||
|
`checkd(so2.sb.rand_mode(), 0); // shared
|
||||||
|
so2.rand_mode(1);
|
||||||
|
`checkd(so1.sa.rand_mode(), 1);
|
||||||
|
`checkd(so1.sb.rand_mode(), 1);
|
||||||
|
|
||||||
|
// ---- Test 8: inline obj.randomize(static_var) save/restore.
|
||||||
|
// The inline form must route through the static rand_mode array, not
|
||||||
|
// the per-instance one (whose index space is different / smaller).
|
||||||
|
s1.sx.rand_mode(1);
|
||||||
|
s1.dy.rand_mode(1);
|
||||||
|
repeat (10) begin
|
||||||
|
rok = s1.randomize(sx); // only sx is randomized, dy frozen
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`check_range(Simple::sx, 1, 11);
|
||||||
|
end
|
||||||
|
// After the inline call, s1.sx.rand_mode() must be back to 1
|
||||||
|
// (the save/restore restores the static array).
|
||||||
|
`checkd(s1.sx.rand_mode(), 1);
|
||||||
|
`checkd(s2.sx.rand_mode(), 1); // shared - also 1
|
||||||
|
|
||||||
|
// ---- Test 9: Base AND Derived each declare own static rand var.
|
||||||
|
// Derived's static array must be sized to fit BOTH base_s and der_s
|
||||||
|
// even though super.new() runs Base's init first.
|
||||||
|
ds1 = new;
|
||||||
|
ds2 = new;
|
||||||
|
`checkd(ds1.base_s.rand_mode(), 1);
|
||||||
|
`checkd(ds1.der_s.rand_mode(), 1);
|
||||||
|
rok = ds1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
if (BaseS::base_s == 0) $stop;
|
||||||
|
if (DerivedS::der_s == 0) $stop;
|
||||||
|
|
||||||
|
// Disable both via per-member call
|
||||||
|
ds1.base_s.rand_mode(0);
|
||||||
|
ds1.der_s.rand_mode(0);
|
||||||
|
`checkd(ds2.base_s.rand_mode(), 0); // shared
|
||||||
|
`checkd(ds2.der_s.rand_mode(), 0); // shared
|
||||||
|
saved_base_s = BaseS::base_s;
|
||||||
|
saved_der_s = DerivedS::der_s;
|
||||||
|
repeat (10) begin
|
||||||
|
rok = ds1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`checkd(BaseS::base_s, saved_base_s);
|
||||||
|
`checkd(DerivedS::der_s, saved_der_s);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Construct a standalone BaseS AFTER DerivedS already initialized the
|
||||||
|
// static array; BaseS init must see size != 0 and skip without
|
||||||
|
// overwriting Derived's prior rand_mode(0) state.
|
||||||
|
bs1 = new;
|
||||||
|
`checkd(bs1.base_s.rand_mode(), 0); // still disabled
|
||||||
|
|
||||||
|
// ---- Test 10: two static rand vars in Base; Derived must accumulate inherited indices.
|
||||||
|
dt1 = new;
|
||||||
|
`checkd(dt1.base2_a.rand_mode(), 1);
|
||||||
|
`checkd(dt1.base2_b.rand_mode(), 1);
|
||||||
|
rok = dt1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
if (BaseTwo::base2_a == 0) $stop;
|
||||||
|
if (BaseTwo::base2_b == 0) $stop;
|
||||||
|
// Disable second static var to prove its index is reachable in the array.
|
||||||
|
dt1.base2_b.rand_mode(0);
|
||||||
|
`checkd(dt1.base2_b.rand_mode(), 0);
|
||||||
|
`checkd(dt1.base2_a.rand_mode(), 1); // first still enabled
|
||||||
|
saved_base2_b = BaseTwo::base2_b;
|
||||||
|
repeat (10) begin
|
||||||
|
rok = dt1.randomize();
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`checkd(BaseTwo::base2_b, saved_base2_b); // disabled - unchanged
|
||||||
|
if (BaseTwo::base2_a == 0) $stop; // still randomizing
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- Test 11: inline randomize-with on class with static rand and no class-level constraints.
|
||||||
|
snc1 = new;
|
||||||
|
snc1.snc_s.rand_mode(1); // ensure static rand-mode array exists
|
||||||
|
repeat (10) begin
|
||||||
|
rok = snc1.randomize() with {
|
||||||
|
snc_d > 5;
|
||||||
|
snc_d < 13;
|
||||||
|
};
|
||||||
|
`checkd(rok, 1);
|
||||||
|
`check_range(snc1.snc_d, 6, 12);
|
||||||
|
end
|
||||||
|
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -1,26 +1,14 @@
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:17:20: Unsupported: 'rand_mode()' on dynamic array element
|
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:16:20: Unsupported: 'rand_mode()' on dynamic array element
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
17 | p.m_dyn_arr[0].rand_mode(0);
|
16 | p.m_dyn_arr[0].rand_mode(0);
|
||||||
| ^~~~~~~~~
|
| ^~~~~~~~~
|
||||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:18:20: Unsupported: 'rand_mode()' on unpacked array element
|
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:17:20: Unsupported: 'rand_mode()' on unpacked array element
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
18 | p.m_unp_arr[0].rand_mode(0);
|
17 | p.m_unp_arr[0].rand_mode(0);
|
||||||
| ^~~~~~~~~
|
| ^~~~~~~~~
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:19:18: Unsupported: 'rand_mode()' on unpacked struct element
|
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:18:18: Unsupported: 'rand_mode()' on unpacked struct element
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
19 | p.m_struct.y.rand_mode(0);
|
18 | p.m_struct.y.rand_mode(0);
|
||||||
| ^~~~~~~~~
|
| ^~~~~~~~~
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:20:16: Unsupported: 'rand_mode()' on static variable
|
|
||||||
: ... note: In instance 't'
|
|
||||||
20 | p.m_static.rand_mode(0);
|
|
||||||
| ^~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:21:55: Unsupported: 'rand_mode()' on static variable
|
|
||||||
: ... note: In instance 't'
|
|
||||||
21 | $display("p.m_static.rand_mode()=%0d", p.m_static.rand_mode());
|
|
||||||
| ^~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:22:7: Unsupported: 'rand_mode()' on static variable: 'm_static'
|
|
||||||
: ... note: In instance 't'
|
|
||||||
22 | p.rand_mode(0);
|
|
||||||
| ^~~~~~~~~
|
|
||||||
%Error: Exiting due to
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ class Packet;
|
||||||
rand int m_dyn_arr[];
|
rand int m_dyn_arr[];
|
||||||
rand int m_unp_arr[10];
|
rand int m_unp_arr[10];
|
||||||
rand struct {int y;} m_struct;
|
rand struct {int y;} m_struct;
|
||||||
static rand int m_static;
|
|
||||||
endclass
|
endclass
|
||||||
|
|
||||||
module t;
|
module t;
|
||||||
|
|
@ -17,8 +16,5 @@ module t;
|
||||||
p.m_dyn_arr[0].rand_mode(0);
|
p.m_dyn_arr[0].rand_mode(0);
|
||||||
p.m_unp_arr[0].rand_mode(0);
|
p.m_unp_arr[0].rand_mode(0);
|
||||||
p.m_struct.y.rand_mode(0);
|
p.m_struct.y.rand_mode(0);
|
||||||
p.m_static.rand_mode(0);
|
|
||||||
$display("p.m_static.rand_mode()=%0d", p.m_static.rand_mode());
|
|
||||||
p.rand_mode(0);
|
|
||||||
end
|
end
|
||||||
endmodule
|
endmodule
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue