Compare commits
13 Commits
23bd30cb96
...
1bc764cce4
| Author | SHA1 | Date |
|---|---|---|
|
|
1bc764cce4 | |
|
|
11bd5fb9ec | |
|
|
57af50499d | |
|
|
46d942f0c3 | |
|
|
06bdfac4a8 | |
|
|
1328127711 | |
|
|
5977760443 | |
|
|
2e6d056755 | |
|
|
5e89fd2c64 | |
|
|
26ea15781f | |
|
|
d32267e15c | |
|
|
410564720c | |
|
|
a043e9b673 |
|
|
@ -505,7 +505,15 @@ void VlRandomizer::hard(std::string&& constraint) {
|
|||
m_constraints.emplace_back(std::move(constraint));
|
||||
}
|
||||
|
||||
void VlRandomizer::clear() { m_constraints.clear(); }
|
||||
void VlRandomizer::clearConstraints() {
|
||||
m_constraints.clear();
|
||||
// Keep m_vars for class member randomization
|
||||
}
|
||||
|
||||
void VlRandomizer::clearAll() {
|
||||
m_constraints.clear();
|
||||
m_vars.clear();
|
||||
}
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlRandomizer::dump() const {
|
||||
|
|
|
|||
|
|
@ -195,9 +195,8 @@ public:
|
|||
};
|
||||
|
||||
//=============================================================================
|
||||
|
||||
// Object holding constraints and variable references.
|
||||
class VlRandomizer final {
|
||||
// VlRandomizer is the object holding constraints and variable references.
|
||||
class VlRandomizer VL_NOT_FINAL {
|
||||
// MEMBERS
|
||||
std::vector<std::string> m_constraints; // Solver-dependent constraints
|
||||
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
|
||||
|
|
@ -570,7 +569,8 @@ public:
|
|||
}
|
||||
|
||||
void hard(std::string&& constraint);
|
||||
void clear();
|
||||
void clearConstraints();
|
||||
void clearAll(); // Clear both constraints and variables
|
||||
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
|
||||
#ifdef VL_DEBUG
|
||||
void dump() const;
|
||||
|
|
@ -578,9 +578,9 @@ public:
|
|||
};
|
||||
|
||||
//=============================================================================
|
||||
|
||||
// Light wrapper for RNG used by std::randomize() to support scope-level randomization.
|
||||
class VlStdRandomizer final {
|
||||
// VlStdRandomizer provides a light wrapper for RNG used by std::randomize()
|
||||
// to support scope-level randomization.
|
||||
class VlStdRandomizer final : public VlRandomizer {
|
||||
// MEMBERS
|
||||
VlRNG m_rng; // Random number generator
|
||||
|
||||
|
|
@ -594,6 +594,7 @@ public:
|
|||
value = VL_MASK_I(width) & VL_RANDOM_RNG_I(m_rng);
|
||||
return true;
|
||||
}
|
||||
bool next() { return VlRandomizer::next(m_rng); }
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
|
|||
|
|
@ -783,7 +783,8 @@ public:
|
|||
FORK_INIT,
|
||||
FORK_JOIN,
|
||||
RANDOMIZER_BASIC_STD_RANDOMIZATION,
|
||||
RANDOMIZER_CLEAR,
|
||||
RANDOMIZER_CLEARCONSTRAINTS,
|
||||
RANDOMIZER_CLEARALL,
|
||||
RANDOMIZER_HARD,
|
||||
RANDOMIZER_WRITE_VAR,
|
||||
RNG_GET_RANDSTATE,
|
||||
|
|
@ -911,7 +912,8 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
|||
{FORK_INIT, "init", false}, \
|
||||
{FORK_JOIN, "join", false}, \
|
||||
{RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \
|
||||
{RANDOMIZER_CLEAR, "clear", false}, \
|
||||
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
|
||||
{RANDOMIZER_CLEARALL, "clearAll", false}, \
|
||||
{RANDOMIZER_HARD, "hard", false}, \
|
||||
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
|
||||
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
|
||||
|
|
|
|||
|
|
@ -144,12 +144,31 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
AstNode* m_constraintExprGenp = nullptr; // Current constraint or constraint if expression
|
||||
AstNodeModule* m_modp; // Current module
|
||||
AstNodeStmt* m_stmtp = nullptr; // Current statement
|
||||
AstNodeFTaskRef* m_stdRandCallp = nullptr; // Current std::randomize() call
|
||||
bool m_inStdWith = false; // True when inside a 'with {}' clause
|
||||
std::set<AstNodeVarRef*> m_staticRefs; // References to static variables under `with` clauses
|
||||
AstWith* m_withp = nullptr; // Current 'with' constraint node
|
||||
std::vector<AstConstraint*> m_clonedConstraints; // List of cloned global constraints
|
||||
std::unordered_set<const AstVar*> m_processedVars; // Track by variable instance, not class
|
||||
|
||||
// METHODS
|
||||
// Check if a variable is listed in std::randomize() arguments
|
||||
bool isVarInStdRandomizeArgs(const AstVar* varp) const {
|
||||
if (!m_inStdWith || !m_stdRandCallp) return false;
|
||||
|
||||
for (AstNode* pinp = m_stdRandCallp->pinsp(); pinp; pinp = pinp->nextp()) {
|
||||
if (VN_IS(pinp, With)) continue;
|
||||
const AstArg* const argp = VN_CAST(pinp, Arg);
|
||||
if (!argp) continue;
|
||||
const AstNodeExpr* const exprp = argp->exprp();
|
||||
if (const AstNodeVarRef* const varrefp = VN_CAST(exprp, NodeVarRef)) {
|
||||
if (varrefp->varp() == varp) return true;
|
||||
} else if (const AstMemberSel* const memberselp = VN_CAST(exprp, MemberSel)) {
|
||||
if (memberselp->varp() == varp) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void markMembers(const AstClass* nodep) {
|
||||
for (const AstClass* classp = nodep; classp;
|
||||
classp = classp->extendsp() ? classp->extendsp()->classp() : nullptr) {
|
||||
|
|
@ -353,6 +372,8 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
if (!nodep->backp()) VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
if (nodep->classOrPackagep() && nodep->classOrPackagep()->name() == "std")
|
||||
m_stdRandCallp = nodep;
|
||||
iterateChildrenConst(nodep);
|
||||
if (nodep->name() == "rand_mode") {
|
||||
AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall);
|
||||
|
|
@ -511,6 +532,7 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
markMembers(classp);
|
||||
}
|
||||
if (nodep->classOrPackagep()->name() == "std") {
|
||||
m_stdRandCallp = nullptr;
|
||||
for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) {
|
||||
AstArg* const argp = VN_CAST(pinp, Arg);
|
||||
if (!argp) continue;
|
||||
|
|
@ -524,6 +546,7 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
AstVarRef* const varrefp = VN_AS(exprp, VarRef);
|
||||
randVarp = varrefp->varp();
|
||||
exprp = nullptr;
|
||||
varrefp->user1(true);
|
||||
}
|
||||
UASSERT_OBJ(randVarp, nodep, "No rand variable found");
|
||||
AstNode* backp = randVarp;
|
||||
|
|
@ -591,16 +614,26 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
|
||||
if (nodep->varp()->lifetime().isStatic()) m_staticRefs.emplace(nodep);
|
||||
|
||||
if (nodep->varp()->rand().isRandomizable()) nodep->user1(true);
|
||||
// Mark as randomizable if: rand-declared, or listed in std::randomize() args
|
||||
if (nodep->varp()->rand().isRandomizable() && !(m_inStdWith && m_stdRandCallp)) {
|
||||
nodep->user1(true);
|
||||
} else if (isVarInStdRandomizeArgs(nodep->varp())) {
|
||||
nodep->user1(true);
|
||||
}
|
||||
}
|
||||
void visit(AstMemberSel* nodep) override {
|
||||
if (!m_constraintExprGenp) return;
|
||||
iterateChildrenConst(nodep);
|
||||
// Member select are randomized when both object and member are marked as rand.
|
||||
// Variable references in with clause are converted to member selects and their from() is
|
||||
// of type AstLambdaArgRef. They are randomized too.
|
||||
const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef);
|
||||
nodep->user1(randObject && nodep->varp()->rand().isRandomizable());
|
||||
const bool randMember = nodep->varp()->rand().isRandomizable();
|
||||
const bool inStdWith = m_inStdWith && m_stdRandCallp;
|
||||
if (randObject && randMember && !inStdWith) {
|
||||
nodep->user1(true);
|
||||
} else if (inStdWith && isVarInStdRandomizeArgs(nodep->varp())) {
|
||||
nodep->user1(true);
|
||||
// Mark parent object for constraint expression visitor
|
||||
if (VN_IS(nodep->fromp(), VarRef)) nodep->fromp()->user1(true);
|
||||
}
|
||||
|
||||
if (m_withp) {
|
||||
AstNode* backp = m_withp;
|
||||
|
|
@ -663,7 +696,13 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
|||
void visit(AstWith* nodep) override {
|
||||
VL_RESTORER(m_withp);
|
||||
m_withp = nodep;
|
||||
for (AstNode* pinp = m_stdRandCallp ? m_stdRandCallp->pinsp() : nullptr; pinp;
|
||||
pinp = pinp->nextp()) {
|
||||
AstWith* const withp = VN_CAST(pinp, With);
|
||||
if (withp == nodep) m_inStdWith = true;
|
||||
}
|
||||
iterateChildrenConst(nodep);
|
||||
m_inStdWith = false;
|
||||
}
|
||||
|
||||
void visit(AstNodeExpr* nodep) override {
|
||||
|
|
@ -908,8 +947,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
dimension = 1;
|
||||
}
|
||||
methodp->dtypeSetVoid();
|
||||
AstClass* const classp
|
||||
= membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class);
|
||||
AstNodeModule* const classp = membersel ? VN_AS(membersel->user2p(), NodeModule)
|
||||
: VN_AS(varp->user2p(), NodeModule);
|
||||
if (membersel) {
|
||||
methodp->addPinsp(membersel);
|
||||
} else {
|
||||
|
|
@ -1356,9 +1395,8 @@ class CaptureVisitor final : public VNVisitor {
|
|||
newVarp->fileline(fileline);
|
||||
newVarp->varType(VVarType::BLOCKTEMP);
|
||||
newVarp->funcLocal(true);
|
||||
newVarp->direction(VDirection::INPUT);
|
||||
newVarp->direction(m_targetp ? VDirection::INPUT : VDirection::REF);
|
||||
newVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
|
||||
m_varCloneMap.emplace(varrefp->varp(), newVarp);
|
||||
varp = newVarp;
|
||||
return true;
|
||||
|
|
@ -1452,6 +1490,7 @@ class CaptureVisitor final : public VNVisitor {
|
|||
m_ignore.emplace(thisRefp);
|
||||
AstMemberSel* const memberSelp
|
||||
= new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()};
|
||||
memberSelp->user1(true);
|
||||
memberSelp->user2p(m_targetp);
|
||||
nodep->replaceWith(memberSelp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
|
|
@ -2051,7 +2090,15 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
AstCMethodHard* const clearp = new AstCMethodHard{
|
||||
fileline,
|
||||
new AstVarRef{fileline, VN_AS(genp->user2p(), NodeModule), genp, VAccess::READWRITE},
|
||||
VCMethod::RANDOMIZER_CLEAR};
|
||||
VCMethod::RANDOMIZER_CLEARCONSTRAINTS};
|
||||
clearp->dtypeSetVoid();
|
||||
return clearp->makeStmt();
|
||||
}
|
||||
AstNodeStmt* implementConstraintsClearAll(FileLine* const fileline, AstVar* const genp) {
|
||||
AstCMethodHard* const clearp = new AstCMethodHard{
|
||||
fileline,
|
||||
new AstVarRef{fileline, VN_AS(genp->user2p(), NodeModule), genp, VAccess::READWRITE},
|
||||
VCMethod::RANDOMIZER_CLEARALL};
|
||||
clearp->dtypeSetVoid();
|
||||
return clearp->makeStmt();
|
||||
}
|
||||
|
|
@ -2579,9 +2626,37 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var),
|
||||
VAccess::WRITE},
|
||||
new AstConst{nodep->fileline(), AstConst::WidthedValue{}, 32, 1}});
|
||||
CaptureVisitor* captured = nullptr;
|
||||
int argn = 0;
|
||||
for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) {
|
||||
AstArg* const argp = VN_CAST(pinp, Arg);
|
||||
AstWith* const withp = VN_CAST(pinp, With);
|
||||
if (withp) {
|
||||
FileLine* const fl = nodep->fileline();
|
||||
// Capture variables in 'with {}' (nullptr = no target class)
|
||||
captured = new CaptureVisitor{withp->exprp(), m_modp, nullptr};
|
||||
captured->addFunctionArguments(randomizeFuncp);
|
||||
// Clear old constraints and variables for std::randomize with clause
|
||||
if (stdrand) {
|
||||
randomizeFuncp->addStmtsp(
|
||||
implementConstraintsClearAll(randomizeFuncp->fileline(), stdrand));
|
||||
}
|
||||
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
||||
randomizeFuncp->addStmtsp(capturedTreep);
|
||||
{
|
||||
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, stdrand,
|
||||
nullptr};
|
||||
}
|
||||
AstCExpr* const solverCallp = new AstCExpr{fl};
|
||||
solverCallp->dtypeSetBit();
|
||||
solverCallp->add(new AstVarRef{fl, stdrand, VAccess::READWRITE});
|
||||
solverCallp->add(".next()");
|
||||
AstVar* const fvarp = VN_AS(randomizeFuncp->fvarp(), Var);
|
||||
AstVarRef* const retvalReadp = new AstVarRef{fl, fvarp, VAccess::READ};
|
||||
AstNodeExpr* const andExprp = new AstAnd{fl, retvalReadp, solverCallp};
|
||||
AstVarRef* const retvalWritep = new AstVarRef{fl, fvarp, VAccess::WRITE};
|
||||
randomizeFuncp->addStmtsp(new AstAssign{fl, retvalWritep, andExprp});
|
||||
}
|
||||
if (!argp) continue;
|
||||
AstNodeExpr* exprp = argp->exprp();
|
||||
|
||||
|
|
@ -2619,6 +2694,8 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
nodep->taskp(randomizeFuncp);
|
||||
nodep->dtypeFrom(randomizeFuncp->dtypep());
|
||||
if (VN_IS(m_modp, Class)) nodep->classOrPackagep(m_modp);
|
||||
if (nodep->pinsp()) pushDeletep(nodep->pinsp()->unlinkFrBackWithNext());
|
||||
if (captured) nodep->addPinsp(captured->getArgs());
|
||||
UINFOTREE(9, nodep, "", "std::rnd-call");
|
||||
UINFOTREE(9, randomizeFuncp, "", "std::rnd-func");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6712,13 +6712,6 @@ class WidthVisitor final : public VNVisitor {
|
|||
for (const AstNode* argp = nodep->pinsp(); argp; argp = argp->nextp())
|
||||
userIterateAndNext(VN_AS(argp, Arg)->exprp(), WidthVP{SELF, BOTH}.p());
|
||||
handleStdRandomizeArgs(nodep); // Provided args should be in current scope
|
||||
if (withp) {
|
||||
nodep->v3warn(CONSTRAINTIGN, "Unsupported: std::randomize()'s 'with'");
|
||||
nodep->replaceWith(new AstConst{nodep->fileline(), 0});
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
VL_DO_DANGLING(pushDeletep(withp), nodep);
|
||||
return;
|
||||
}
|
||||
processFTaskRefArgs(nodep);
|
||||
nodep->addPinsp(withp);
|
||||
nodep->didWidth(true);
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
%Warning-CONSTRAINTIGN: t/t_std_randomize_unsup_bad.v:11:16: Unsupported: std::randomize()'s 'with'
|
||||
: ... note: In instance 't'
|
||||
11 | if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop;
|
||||
| ^~~~~~~~~
|
||||
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
|
||||
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2024 by Antmicro Ltd.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
int a, b;
|
||||
if (std::randomize(a, b) != 1) $stop;
|
||||
if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop;
|
||||
if (!(2 < a && a < 7 && b < a)) $stop;
|
||||
$write("-*-* All Finished *-*-\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -9,8 +9,13 @@
|
|||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
if not test.have_solver:
|
||||
test.skip("No constraint solver installed")
|
||||
|
||||
test.compile()
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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
|
||||
|
||||
class external_cl;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
function new();
|
||||
x = 0;
|
||||
y = 0;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
int a, b;
|
||||
int limit = 10;
|
||||
external_cl obj;
|
||||
|
||||
// Test 1: Basic std::randomize with 'with' clause
|
||||
if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop;
|
||||
if (!(2 < a && a < 7 && b < a)) $stop;
|
||||
$display("Test 1 passed: a=%0d, b=%0d", a, b);
|
||||
|
||||
// Test 2: Local variable and class member with mutual constraints
|
||||
obj = new;
|
||||
if (std::randomize(a, obj.x) with { a > 10; a < 20; obj.x > a; obj.x < a + 5; } != 1) $stop;
|
||||
if (!(a > 10 && a < 20 && obj.x > a && obj.x < a + 5)) $stop;
|
||||
$display("Test 2 passed: a=%0d, obj.x=%0d (obj.x between a+1 and a+4)", a, obj.x);
|
||||
|
||||
// Test 3: Reference external variable in constraint
|
||||
if (std::randomize(a) with { a > 0; a < limit; } != 1) $stop;
|
||||
if (!(a > 0 && a < limit)) $stop;
|
||||
$display("Test 3 passed: a=%0d, limit=%0d", a, limit);
|
||||
|
||||
// Test 4: Randomize class member variables
|
||||
obj = new;
|
||||
if (std::randomize(obj.x, obj.y) with { obj.x > 5; obj.x < 20; obj.y == obj.x + 1; } != 1) $stop;
|
||||
if (!(obj.x > 5 && obj.x < 20 && obj.y == obj.x + 1)) $stop;
|
||||
$display("Test 4 passed: obj.x=%0d, obj.y=%0d", obj.x, obj.y);
|
||||
|
||||
// Test 5: Multiple class members and local variable
|
||||
if (std::randomize(a, obj.x, obj.y) with { a > 0; a < 5; obj.x > a; obj.y > obj.x; obj.y < a + 10; } != 1) $stop;
|
||||
if (!(a > 0 && a < 5 && obj.x > a && obj.y > obj.x && obj.y < a + 10)) $stop;
|
||||
$display("Test 5 passed: a=%0d, obj.x=%0d, obj.y=%0d", a, obj.x, obj.y);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue