Support `unique` constraints (on 1D static arrays) (#6810) (#6878)

This commit is contained in:
Srinivasan Venkataramanan 2026-01-16 19:12:09 +05:30 committed by GitHub
parent 550cf4462d
commit 6fc9089a77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 234 additions and 21 deletions

View File

@ -367,7 +367,26 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
}
bool VlRandomizer::next(VlRNG& rngr) {
if (m_vars.empty()) return true;
if (m_vars.empty() && m_unique_arrays.empty()) return true;
for (const std::string& baseName : m_unique_arrays) {
const auto it = m_vars.find(baseName);
// Look up the actual size we stored earlier
// const uint32_t size = m_unique_array_sizes[baseName];
const uint32_t size = m_unique_array_sizes.at(baseName);
if (it != m_vars.end()) {
std::string distinctExpr = "(__Vbv (distinct";
for (uint32_t i = 0; i < size; ++i) {
char hexIdx[12];
sprintf(hexIdx, "#x%08x", i);
distinctExpr += " (select " + it->first + " " + hexIdx + ")";
}
distinctExpr += "))";
m_constraints.push_back(distinctExpr);
}
}
std::iostream& os = getSolver();
if (!os) return false;
@ -384,6 +403,7 @@ bool VlRandomizer::next(VlRNG& rngr) {
var.second->emitType(os);
os << ")\n";
}
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}

View File

@ -195,7 +195,6 @@ public:
};
//=============================================================================
// Object holding constraints and variable references.
class VlRandomizer VL_NOT_FINAL {
// MEMBERS
@ -205,6 +204,8 @@ class VlRandomizer VL_NOT_FINAL {
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
std::vector<std::string> m_unique_arrays;
std::map<std::string, uint32_t> m_unique_array_sizes;
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
int m_index = 0; // Internal counter for key generation
@ -317,10 +318,10 @@ public:
}
// --- write_var to register variables ---
// Register scalar variable (non-struct, basic type)
template <typename T>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
typename std::enable_if<!VlContainsCustomStruct<T>::value && !IsVlUnpacked<T>::value,
void>::type
write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
@ -358,21 +359,23 @@ public:
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (dimension > 0) record_struct_arr(var, name, dimension, {}, {});
}
// Register unpacked array of non-struct types
template <typename T, std::size_t N_Depth>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(VlUnpacked<T, N_Depth>& var, int width, const char* name, int dimension,
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 (dimension > 0) {
m_index = 0;
record_arr_table(var, name, dimension, {}, {});
}
}
// Register unpacked array of structs
template <typename T, std::size_t N_Depth>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
@ -416,6 +419,12 @@ public:
++m_index;
}
// This is the "Sender" API for the generated code
void rand_unique(const std::string& name, uint32_t size) {
m_unique_arrays.push_back(name);
m_unique_array_sizes[name] = size;
}
// Recursively record all elements in an unpacked array
template <typename T, std::size_t N_Depth>
void record_arr_table(VlUnpacked<T, N_Depth>& var, const std::string& name, int dimension,

View File

@ -1612,6 +1612,11 @@ private:
return a != b;
}
};
// Trait to detect VlUnpacked types
template <typename T>
struct IsVlUnpacked : std::false_type {};
template <typename T, std::size_t N>
struct IsVlUnpacked<VlUnpacked<T, N>> : std::true_type {};
template <typename T_Value, std::size_t N_Depth>
std::string VL_TO_STRING(const VlUnpacked<T_Value, N_Depth>& obj) {

View File

@ -787,6 +787,7 @@ public:
RANDOMIZER_CLEARCONSTRAINTS,
RANDOMIZER_CLEARALL,
RANDOMIZER_HARD,
RANDOMIZER_UNIQUE,
RANDOMIZER_WRITE_VAR,
RNG_GET_RANDSTATE,
RNG_SET_RANDSTATE,
@ -916,6 +917,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
{RANDOMIZER_CLEARALL, "clearAll", false}, \
{RANDOMIZER_HARD, "hard", false}, \
{RANDOMIZER_UNIQUE, "rand_unique", 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

@ -725,6 +725,7 @@ class ConstraintExprVisitor final : public VNVisitor {
// AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module
// VNuser3InUse m_inuser3; (Allocated for use in RandomizeVisitor)
AstClass* const m_classp;
AstNodeFTask* const m_inlineInitTaskp; // Method to add write_var calls to
// (may be null, then new() is used)
AstVar* const m_genp; // VlRandomizer variable of the class
@ -1336,9 +1337,83 @@ class ConstraintExprVisitor final : public VNVisitor {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstConstraintUnique* nodep) override {
nodep->v3warn(CONSTRAINTIGN, "Constraint expression ignored (unsupported)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
if (!m_classp) return;
FileLine* const fl = nodep->fileline();
AstNodeFTask* const initTaskp = VN_AS(m_memberMap.findMember(m_classp, "new"), NodeFTask);
if (!initTaskp) return;
AstVar* const genVarp = VN_AS(m_classp->user3p(), Var);
if (!genVarp) return;
for (AstNode* itemp = nodep->rangesp(); itemp; itemp = itemp->nextp()) {
if (AstVarRef* const varRefp = VN_CAST(itemp, VarRef)) {
AstVar* const varp = varRefp->varp();
AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
if (AstUnpackArrayDType* const up = VN_CAST(dtypep, UnpackArrayDType)) {
AstRange* const rangep = up->rangep();
if (!rangep || !VN_IS(rangep->leftp(), Const)
|| !VN_IS(rangep->rightp(), Const)) {
nodep->v3warn(
CONSTRAINTIGN,
"Unsupported: Unique constraint on other than static arrays");
continue;
}
// Ensure it is ONLY 1-D by checking that the sub-type is NOT an array/queue
// We skip refs (typedefs) to see the actual underlying type
AstNodeDType* const subp = up->subDTypep()->skipRefp();
if (VN_IS(subp, NodeArrayDType) || VN_IS(subp, QueueDType)
|| VN_IS(subp, DynArrayDType)) {
nodep->v3warn(
CONSTRAINTIGN,
"Unsupported: Unique constraint on other than static arrays");
continue;
}
} else {
nodep->v3warn(CONSTRAINTIGN,
"Unsupported: Unique constraint on other than static arrays");
continue;
}
AstCMethodHard* const wCallp = new AstCMethodHard{
fl, new AstVarRef{fl, genVarp, VAccess::READ}, VCMethod::RANDOMIZER_WRITE_VAR};
wCallp->addPinsp(new AstVarRef{fl, varp, VAccess::READ});
wCallp->addPinsp(new AstConst{fl, AstConst::Unsized64{},
static_cast<uint64_t>(varp->dtypep()->width())});
wCallp->addPinsp(new AstConst{fl, AstConst::String{}, varp->name()});
wCallp->addPinsp(new AstConst{fl, 1}); // Dimension
wCallp->dtypeSetVoid();
initTaskp->addStmtsp(new AstStmtExpr{fl, wCallp});
uint32_t arraySize = 0;
if (AstUnpackArrayDType* const adtypep
= VN_CAST(varp->dtypep(), UnpackArrayDType)) {
arraySize = adtypep->elementsConst();
}
if (arraySize > 100) {
nodep->v3warn(CONSTRAINTIGN,
"Unsupported: Unique constraint on static arrays of size > 100");
continue;
}
AstNodeExpr* const uPins = new AstConst{fl, AstConst::String{}, varp->name()};
uPins->addNext(new AstConst{fl, arraySize});
AstCMethodHard* const uCallp
= new AstCMethodHard{fl, new AstVarRef{fl, genVarp, VAccess::READ},
VCMethod::RANDOMIZER_UNIQUE, uPins};
uCallp->dtypep(nodep->findVoidDType());
initTaskp->addStmtsp(new AstStmtExpr{fl, uCallp});
}
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstConstraintExpr* nodep) override {
iterateChildren(nodep);
if (m_wantSingle) {
@ -1440,10 +1515,11 @@ class ConstraintExprVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep,
explicit ConstraintExprVisitor(AstClass* classp, VMemberMap& memberMap, AstNode* nodep,
AstNodeFTask* inlineInitTaskp, AstVar* genp,
AstVar* randModeVarp, std::set<std::string>& writtenVars)
: m_inlineInitTaskp{inlineInitTaskp}
: m_classp{classp}
, m_inlineInitTaskp{inlineInitTaskp}
, m_genp{genp}
, m_randModeVarp{randModeVarp}
, m_memberMap{memberMap}
@ -2579,8 +2655,8 @@ class RandomizeVisitor final : public VNVisitor {
resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt());
}
ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars};
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars};
if (constrp->itemsp()) {
taskp->addStmtsp(wrapIfConstraintMode(
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
@ -2833,8 +2909,8 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
stdrand, nullptr, m_writtenVars};
ConstraintExprVisitor{nullptr, m_memberMap, capturedTreep, randomizeFuncp,
stdrand, nullptr, m_writtenVars};
}
AstCExpr* const solverCallp = new AstCExpr{fl};
solverCallp->dtypeSetBit();
@ -2961,8 +3037,8 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
localGenp, randModeVarp, m_writtenVars};
ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp,
localGenp, randModeVarp, m_writtenVars};
}
// Call the solver and set return value

View File

@ -0,0 +1,23 @@
%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:19:5: Unsupported: Unique constraint on static arrays of size > 100
: ... note: In instance 't'
19 | unique {uniq_val_arr_400};
| ^~~~~~
... 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_constraint_unsup_unq_arr.v:22:5: Unsupported: Unique constraint on other than static arrays
: ... note: In instance 't'
22 | unique {uniq_val_darr};
| ^~~~~~
%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:23:5: Unsupported: Unique constraint on other than static arrays
: ... note: In instance 't'
23 | unique {uniq_val_hash};
| ^~~~~~
%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:24:5: Unsupported: Unique constraint on other than static arrays
: ... note: In instance 't'
24 | unique {uniq_val_queue};
| ^~~~~~
%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:25:5: Unsupported: Unique constraint on other than static arrays
: ... note: In instance 't'
25 | unique {uniq_val_arr_mda};
| ^~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,66 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by AsFigo.
// SPDX-License-Identifier: CC0-1.0
class UniqueMultipleArray;
rand bit [15:0] uniq_val_arr[4];
rand bit [15:0] uniq_val_arr_400[400];
rand bit [15:0] uniq_val_arr_mda[4][];
rand bit [15:0] uniq_val_darr[];
rand bit [15:0] uniq_val_hash[int];
rand bit [15:0] uniq_val_queue[$];
rand bit b1;
rand int array[2]; // 2,4,6 // TODO: add rand when supported
// Constraint to ensure the elements in the array are unique
constraint unique_c {
unique {uniq_val_arr}; // Ensure unique values in the array
unique {uniq_val_arr_400}; // Ensure unique values in the array
}
constraint unique_c1 {
unique {uniq_val_darr}; // Ensure unique values in the array
unique {uniq_val_hash}; // Ensure unique values in the array
unique {uniq_val_queue}; // Ensure unique values in the array
unique {uniq_val_arr_mda}; // Ensure unique values in the array
unique { array[0], array[1] };
}
// --------------------------------------------------
// Explicit uniqueness checker (post-solve validation)
// --------------------------------------------------
function bit check_unique();
for (int i = 0; i < $size(uniq_val_arr); i++) begin
for (int j = i + 1; j < $size(uniq_val_arr); j++) begin
if (uniq_val_arr[i] == uniq_val_arr[j]) begin
$error("UNIQUENESS VIOLATION: uniq_val_arr[%0d] == uniq_val_arr[%0d] == 0x%h", i, j, uniq_val_arr[i]);
return 0;
end
end
end
return 1;
endfunction
function void post_randomize();
$display("Randomized values in uniq_val_arr: %p", uniq_val_arr);
if (!check_unique()) begin
$fatal(1, "Post-randomize uniqueness check FAILED");
end
foreach (uniq_val_arr[i]) begin
$display("uniq_val_arr[%0d] = 0x%h", i, uniq_val_arr[i]);
end
endfunction
endclass : UniqueMultipleArray
module t;
initial begin
// Create an instance of the UniqueMultipleArray class
UniqueMultipleArray array_instance = new();
// Attempt to randomize and verify the constraints
/* verilator lint_off WIDTHTRUNC */
assert (array_instance.randomize())
else $error("Randomization failed!");
end
endmodule : t

View File

@ -4,10 +4,6 @@
| ^~~~
... 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.v:40:7: Constraint expression ignored (unsupported)
: ... note: In instance 't'
40 | unique { array[0], array[1] };
| ^~~~~~
%Warning-CONSTRAINTIGN: t/t_randomize.v:43:23: Constraint expression ignored (imperfect distribution)
: ... note: In instance 't'
43 | constraint order { solve length before header; }