Fix randomize size+element queue constraints (#5582) (#7225)

This commit is contained in:
Rahul Behl 2026-03-11 16:21:32 +05:30 committed by GitHub
parent 4e6eafd994
commit cd8818ca23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 280 additions and 37 deletions

View File

@ -88,6 +88,7 @@ public:
m_arrVarsRefp = arrVarsRefp;
}
mutable std::map<std::string, int> count_cache;
void clearCountCache() const { count_cache.clear(); }
int countMatchingElements(const ArrayInfoMap& arr_vars, const std::string& base_name) const {
if (VL_LIKELY(count_cache.find(base_name) != count_cache.end()))
return count_cache[base_name];
@ -172,7 +173,14 @@ public:
for (int i = 0; i < dimension(); ++i) s << ")";
}
} else {
VL_FATAL_MT(__FILE__, __LINE__, "randomize", "indexed_name not found in m_arr_vars");
if (dimension() > 0) {
for (int i = 0; i < dimension(); ++i) s << "(Array (_ BitVec 32) ";
s << "(_ BitVec " << width() << ")";
for (int i = 0; i < dimension(); ++i) s << ")";
} else {
VL_FATAL_MT(__FILE__, __LINE__, "randomize",
"indexed_name not found in m_arr_vars");
}
}
}
int totalWidth() const override {
@ -355,12 +363,15 @@ public:
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(VlQueue<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;
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T>>>(
name, width, &var, dimension, randmodeIdx);
if (m_vars.find(name) == m_vars.end()) {
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T>>>(
name, width, &var, dimension, randmodeIdx);
}
if (dimension > 0) {
m_index = 0;
clear_arr_table(name);
record_arr_table(var, name, dimension, {}, {});
m_vars[name]->clearCountCache();
}
}
@ -377,15 +388,17 @@ public:
write_var(VlUnpacked<T, N_Depth>& var, uint32_t width, const std::string& name,
uint32_t dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlUnpacked<T, N_Depth>>>(
name, width, &var, dimension, randmodeIdx);
if (m_vars.find(name) == m_vars.end()) {
m_vars[name]
= std::make_shared<const VlRandomArrayVarTemplate<VlUnpacked<T, N_Depth>>>(
name, width, &var, dimension, randmodeIdx);
}
if (dimension > 0) {
m_index = 0;
clear_arr_table(name);
record_arr_table(var, name, dimension, {}, {});
m_vars[name]->clearCountCache();
}
}
// Register unpacked array of structs
@ -401,13 +414,16 @@ public:
typename std::enable_if<!VlContainsCustomStruct<T_Value>::value, void>::type
write_var(VlAssocArray<T_Key, T_Value>& 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;
m_vars[name]
= std::make_shared<const VlRandomArrayVarTemplate<VlAssocArray<T_Key, T_Value>>>(
name, width, &var, dimension, randmodeIdx);
if (m_vars.find(name) == m_vars.end()) {
m_vars[name]
= std::make_shared<const VlRandomArrayVarTemplate<VlAssocArray<T_Key, T_Value>>>(
name, width, &var, dimension, randmodeIdx);
}
if (dimension > 0) {
m_index = 0;
clear_arr_table(name);
record_arr_table(var, name, dimension, {}, {});
m_vars[name]->clearCountCache();
}
}
@ -592,10 +608,25 @@ public:
+ std::to_string(idx);
}
// Helper: Clear existing array element entries for a base name
void clear_arr_table(const std::string& name) {
for (int index = 0;; ++index) {
const std::string key = generateKey(name, index);
const auto it = m_arr_vars.find(key);
if (it == m_arr_vars.end()) break;
m_arr_vars.erase(it);
}
}
void hard(std::string&& constraint, const char* filename = "", uint32_t linenum = 0,
const char* source = "");
void soft(std::string&& constraint, const char* filename = "", uint32_t linenum = 0,
const char* source = "");
void pin_var(const char* name, int width, uint64_t value) {
std::string constraint = "(__Vbv (= "s + name + " (_ bv" + std::to_string(value) + " "
+ std::to_string(width) + ")))";
hard(std::move(constraint));
}
void disable_soft(const std::string& varName);
void clearConstraints();
void clearAll(); // Clear both constraints and variables

View File

@ -821,6 +821,7 @@ public:
RANDOMIZER_UNIQUE,
RANDOMIZER_MARK_RANDC,
RANDOMIZER_SOLVE_BEFORE,
RANDOMIZER_PIN_VAR,
RANDOMIZER_WRITE_VAR,
RNG_GET_RANDSTATE,
RNG_SET_RANDSTATE,
@ -959,6 +960,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{RANDOMIZER_UNIQUE, "rand_unique", false}, \
{RANDOMIZER_MARK_RANDC, "markRandc", false}, \
{RANDOMIZER_SOLVE_BEFORE, "solveBefore", false}, \
{RANDOMIZER_PIN_VAR, "pin_var", false}, \
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \

View File

@ -740,6 +740,7 @@ class ConstraintExprVisitor final : public VNVisitor {
// (used to format "%@.%@" for struct arrays)
std::set<std::string>& m_writtenVars; // Track which variable paths have write_var generated
// (shared across all constraints)
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
@ -953,9 +954,14 @@ class ConstraintExprVisitor final : public VNVisitor {
void visit(AstNodeVarRef* nodep) override {
AstVar* varp = nodep->varp();
if (varp->user4p()) {
varp->user4p()->v3warn(
CONSTRAINTIGN,
"Size constraint combined with element constraint may not work correctly");
bool isSizeRef = false;
if (AstCMethodHard* const methodp = VN_CAST(nodep->backp(), CMethodHard)) {
if (methodp->method() == VCMethod::ASSOC_SIZE
|| methodp->method() == VCMethod::DYN_SIZE) {
isSizeRef = true;
}
}
if (!isSizeRef && m_sizeConstrainedArraysp) { m_sizeConstrainedArraysp->insert(varp); }
}
// Check if this variable is marked as globally constrained
@ -2394,14 +2400,16 @@ public:
explicit ConstraintExprVisitor(AstClass* classp, VMemberMap& memberMap, AstNode* nodep,
AstNodeFTask* inlineInitTaskp, AstVar* genp,
AstVar* randModeVarp, std::set<std::string>& writtenVars,
AstNodeFTask* memberselInitTaskp = nullptr)
AstNodeFTask* memberselInitTaskp = nullptr,
std::set<AstVar*>* sizeConstrainedArraysp = nullptr)
: m_classp{classp}
, m_inlineInitTaskp{inlineInitTaskp}
, m_memberselInitTaskp{memberselInitTaskp}
, m_genp{genp}
, m_randModeVarp{randModeVarp}
, m_memberMap{memberMap}
, m_writtenVars{writtenVars} {
, m_writtenVars{writtenVars}
, m_sizeConstrainedArraysp{sizeConstrainedArraysp} {
iterateAndNextNull(nodep);
}
};
@ -2699,6 +2707,7 @@ class RandomizeVisitor final : public VNVisitor {
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
AstConstraint* m_constraintp = nullptr; // Current constraint
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
std::map<AstClass*, std::set<AstVar*>> m_sizeConstrainedArrays; // Per-class arrays
std::map<AstClass*, AstVar*>
m_staticConstraintModeVars; // Static constraint mode vars per class
@ -3985,8 +3994,10 @@ class RandomizeVisitor final : public VNVisitor {
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
if (constrp->itemsp()) lowerDistConstraints(taskp, constrp->itemsp());
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars, randomizep};
std::set<AstVar*>& sizeArrays = m_sizeConstrainedArrays[classp];
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(),
nullptr, genp, randModeVarp,
m_writtenVars, randomizep, &sizeArrays};
if (constrp->itemsp()) {
taskp->addStmtsp(wrapIfConstraintMode(
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
@ -4090,7 +4101,106 @@ class RandomizeVisitor final : public VNVisitor {
solverCallp->dtypeSetBit();
solverCallp->add(new AstVarRef{fl, genModp, genp, VAccess::READWRITE});
solverCallp->add(".next(__Vm_rng)");
beginValp = solverCallp;
const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
const bool needsSizePhase
= sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty();
if (needsSizePhase) {
AstVar* const sizeOkVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__Vsize_ok",
nodep->findBasicDType(VBasicDTypeKwd::BIT)};
sizeOkVarp->funcLocal(true);
randomizep->addStmtsp(sizeOkVarp);
AstVar* const finalOkVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__Vfinal_ok",
nodep->findBasicDType(VBasicDTypeKwd::BIT)};
finalOkVarp->funcLocal(true);
randomizep->addStmtsp(finalOkVarp);
// First pass: solve size variables (and other constraints) to determine sizes
randomizep->addStmtsp(
new AstAssign{fl, new AstVarRef{fl, sizeOkVarp, VAccess::WRITE}, solverCallp});
// Resize constrained arrays before rebuilding constraints
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp};
AstIf* const ifp = new AstIf{fl, new AstVarRef{fl, sizeOkVarp, VAccess::READ},
resizeTaskRefp->makeStmt(), nullptr};
randomizep->addStmtsp(ifp);
}
// Refresh array element tables after resize
for (AstVar* const arrVarp : sizeArraysIt->second) {
AstCMethodHard* const methodp = new AstCMethodHard{
fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_WRITE_VAR};
methodp->dtypeSetVoid();
AstNodeModule* const classp = VN_AS(arrVarp->user2p(), NodeModule);
AstVarRef* const varRefp = new AstVarRef{fl, classp, arrVarp, VAccess::WRITE};
varRefp->classOrPackagep(classp);
methodp->addPinsp(varRefp);
uint32_t dimension = 0;
if (VN_IS(arrVarp->dtypep(), UnpackArrayDType)
|| VN_IS(arrVarp->dtypep(), DynArrayDType)
|| VN_IS(arrVarp->dtypep(), QueueDType)
|| VN_IS(arrVarp->dtypep(), AssocArrayDType)) {
const std::pair<uint32_t, uint32_t> dims
= arrVarp->dtypep()->dimensions(/*includeBasic=*/true);
dimension = dims.second;
}
AstNodeDType* tmpDtypep = arrVarp->dtypep();
while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType)
|| VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType))
tmpDtypep = tmpDtypep->subDTypep();
const size_t width = tmpDtypep->width();
methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, width});
AstNodeExpr* const varnamep = new AstCExpr{
fl, AstCExpr::Pure{}, "\"" + arrVarp->name() + "\"", arrVarp->width()};
varnamep->dtypep(arrVarp->dtypep());
methodp->addPinsp(varnamep);
methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, dimension});
randomizep->addStmtsp(methodp->makeStmt());
}
// Rebuild constraints after resize and pin size variables
randomizep->addStmtsp(implementConstraintsClear(fl, genp));
AstTaskRef* const setupTaskRefp2 = new AstTaskRef{fl, setupAllTaskp};
randomizep->addStmtsp(setupTaskRefp2->makeStmt());
for (AstVar* const arrVarp : sizeArraysIt->second) {
AstVar* const sizeVarp = VN_CAST(arrVarp->user4p(), Var);
if (!sizeVarp) continue;
AstCMethodHard* const pinp = new AstCMethodHard{
fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_PIN_VAR};
pinp->dtypeSetVoid();
AstCExpr* const namep
= new AstCExpr{fl, AstCExpr::Pure{}, "\"" + sizeVarp->name() + "\""};
namep->dtypeSetUInt32();
pinp->addPinsp(namep);
pinp->addPinsp(new AstConst{fl, AstConst::Unsized64{},
static_cast<uint64_t>(sizeVarp->width())});
pinp->addPinsp(new AstVarRef{fl, sizeVarp, VAccess::READ});
randomizep->addStmtsp(pinp->makeStmt());
}
// Final pass: solve full constraints with sizes pinned
AstCExpr* const solverCallp2 = new AstCExpr{fl};
solverCallp2->dtypeSetBit();
solverCallp2->add(new AstVarRef{fl, genModp, genp, VAccess::READWRITE});
solverCallp2->add(".next(__Vm_rng)");
randomizep->addStmtsp(new AstAssign{
fl, new AstVarRef{fl, finalOkVarp, VAccess::WRITE}, solverCallp2});
beginValp = new AstAnd{fl, new AstVarRef{fl, sizeOkVarp, VAccess::READ},
new AstVarRef{fl, finalOkVarp, VAccess::READ}};
} else {
beginValp = solverCallp;
}
if (randModeVarp) {
AstNodeModule* const randModeClassp = VN_AS(randModeVarp->user2p(), Class);
AstNodeFTask* const newp
@ -4105,10 +4215,15 @@ class RandomizeVisitor final : public VNVisitor {
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp};
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
const bool needsSizePhase
= sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty();
if (!needsSizePhase) {
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp};
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
}
}
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
@ -4321,7 +4436,7 @@ class RandomizeVisitor final : public VNVisitor {
{
expandUniqueElementList(capturedTreep);
ConstraintExprVisitor{nullptr, m_memberMap, capturedTreep, randomizeFuncp,
stdrand, nullptr, m_writtenVars};
stdrand, nullptr, m_writtenVars, nullptr};
}
AstCExpr* const solverCallp = new AstCExpr{fl};
solverCallp->dtypeSetBit();
@ -4455,7 +4570,7 @@ class RandomizeVisitor final : public VNVisitor {
{
expandUniqueElementList(capturedTreep);
ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp,
localGenp, randModeVarp, m_writtenVars};
localGenp, randModeVarp, m_writtenVars, nullptr};
}
// Call the solver and set return value

View File

@ -3,16 +3,12 @@
| ^~~~
... 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.
%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:27:7: Size constraint combined with element constraint may not work correctly
: ... note: In instance 't'
27 | q.size < 5;
| ^~~~
%Error-UNSUPPORTED: t/t_randomize_method_types_unsup.v:15:12: Unsupported: random member variable with the type of the containing class
: ... note: In instance 't'
15 | rand Cls cls;
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:43:41: Unsupported: randomizing this expression, treating as state
43 | res = obj.randomize() with { dynarr.size > 2; };
%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:39:41: Unsupported: randomizing this expression, treating as state
39 | res = obj.randomize() with { dynarr.size > 2; };
| ^~~~
%Error: Exiting due to

View File

@ -23,10 +23,6 @@ class Cls;
dynarr[1].size < 10;
}
constraint statedep { i < st + 2; }
constraint q_size_elem {
q.size < 5;
q[i] < 10;
}
constraint global_constraint {
foo.x < y;
}

View File

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

View File

@ -0,0 +1,82 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026
// SPDX-License-Identifier: CC0-1.0
`define check_rand(cl, field, cond) \
begin \
automatic longint prev_result; \
automatic int ok; \
if (!bit'(cl.randomize())) $stop; \
prev_result = longint'(field); \
if (!(cond)) $stop; \
repeat(20) begin \
longint result; \
if (!bit'(cl.randomize())) $stop; \
result = longint'(field); \
if (!(cond)) $stop; \
if (result != prev_result) ok = 1; \
prev_result = result; \
end \
if (ok != 1) $stop; \
end
class SizeElemQ;
rand int q[$];
constraint c {
q.size() > 1;
q.size() < 6;
q[0] > 100;
q[1] == q[0] + 1;
}
endclass
class SizeElemQ2;
rand int q[$];
rand int min_sz;
constraint c {
min_sz inside {[2:4]};
q.size() == min_sz;
foreach (q[i]) q[i] inside {[10:20]};
q[0] != q[q.size() - 1];
}
endclass
class SizeElemDynArr;
rand int da[];
constraint c {
da.size() inside {[3:5]};
da[0] < 50;
da[da.size() - 1] > 50;
foreach (da[i]) da[i] inside {[1:100]};
}
endclass
module t;
initial begin
automatic SizeElemQ obj = new;
automatic SizeElemQ2 obj2 = new;
automatic SizeElemDynArr obj3 = new;
`check_rand(obj, obj.q[0],
obj.q.size() > 1 && obj.q.size() < 6
&& obj.q[0] > 100
&& obj.q[1] == obj.q[0] + 1);
`check_rand(obj2, obj2.q[0],
obj2.min_sz inside {[2:4]}
&& obj2.q.size() == obj2.min_sz
&& obj2.q[0] inside {[10:20]}
&& obj2.q[obj2.q.size() - 1] inside {[10:20]}
&& obj2.q[0] != obj2.q[obj2.q.size() - 1]);
`check_rand(obj3, obj3.da[0],
obj3.da.size() inside {[3:5]}
&& obj3.da[0] < 50
&& obj3.da[obj3.da.size() - 1] > 50);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule