Compare commits

...

13 Commits

Author SHA1 Message Date
Yilou Wang 1bc764cce4
Merge 11bd5fb9ec into 9d74984163 2025-11-07 09:28:42 +00:00
Yilou Wang 11bd5fb9ec fix conflicts 2025-11-07 10:28:30 +01:00
Yilou Wang 57af50499d
Merge branch 'master' into std_with_rand 2025-11-06 15:59:11 +01:00
Yilou Wang 46d942f0c3 reform coding format 2025-11-06 15:08:07 +01:00
Yilou Wang 06bdfac4a8 fix clear error and update the tests 2025-10-20 19:32:01 +02:00
Yilou Wang 1328127711 refine code 2025-10-11 23:48:01 +02:00
Yilou Wang 5977760443 update tests 2025-10-11 23:04:56 +02:00
github action 2e6d056755 Apply 'make format' 2025-09-28 12:56:10 +00:00
Udaya Raj Subedi 5e89fd2c64 resolve conflict 2025-09-28 14:52:53 +02:00
github action 26ea15781f Apply 'make format' 2025-09-28 10:47:54 +00:00
Udaya Raj Subedi d32267e15c Variable in the arguments of the std::randomization are randomized if not mentioned their value will be checked against the constrained 2025-09-28 12:46:35 +02:00
github action 410564720c Apply 'make format' 2025-09-21 10:23:37 +00:00
Udaya Raj Subedi a043e9b673 Initial work of the std::rand with {} 2025-09-21 12:20:19 +02:00
9 changed files with 168 additions and 52 deletions

View File

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

View File

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

View File

@ -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}, \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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