Support unique constraint on explicit array element subsets

Expand `unique { arr[i], arr[j], ... }` into pairwise != constraints
before ConstraintExprVisitor runs, so the existing constraint machinery
handles element-level unique naturally. Also mark AstConstraintUnique
children in RandomizeMarkVisitor so VarRefs get the rand-dependent flag.

Whole-array `unique { arr }` still uses the existing rand_unique runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yilou Wang 2026-02-12 13:45:56 +01:00
parent 446bec3d1a
commit e3c597f84d
3 changed files with 158 additions and 0 deletions

View File

@ -604,6 +604,14 @@ class RandomizeMarkVisitor final : public VNVisitor {
handleRandomizeArgument(argp->exprp(), fromVarp, false);
}
}
void visit(AstConstraintUnique* nodep) override {
VL_RESTORER(m_stmtp);
VL_RESTORER(m_constraintExprGenp);
m_stmtp = nodep;
m_constraintExprGenp = nodep;
iterateChildren(nodep);
if (!nodep->backp()) VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstConstraintExpr* nodep) override {
VL_RESTORER(m_constraintExprGenp);
m_constraintExprGenp = nodep;
@ -2161,6 +2169,53 @@ class RandomizeVisitor final : public VNVisitor {
}
return false;
}
// Expand AstConstraintUnique with element-list items into pairwise != constraints.
// Whole-array VarRef unique constraints are left untouched for ConstraintExprVisitor.
static void expandUniqueElementList(AstNode* itemsp) {
AstNode* itemp = itemsp;
while (itemp) {
AstNode* const nextp = itemp->nextp();
AstConstraintUnique* const uniquep = VN_CAST(itemp, ConstraintUnique);
if (!uniquep) {
itemp = nextp;
continue;
}
// Collect non-array-VarRef items
std::vector<AstNodeExpr*> exprItems;
bool hasArrayVarRef = false;
for (AstNode* rp = uniquep->rangesp(); rp; rp = rp->nextp()) {
if (AstVarRef* const vrp = VN_CAST(rp, VarRef)) {
if (VN_IS(vrp->varp()->dtypep()->skipRefp(), UnpackArrayDType)) {
hasArrayVarRef = true;
continue; // Skip whole-array items
}
}
exprItems.push_back(VN_AS(rp, NodeExpr));
}
if (exprItems.size() >= 2) {
FileLine* const fl = uniquep->fileline();
for (size_t i = 0; i < exprItems.size(); i++) {
for (size_t j = i + 1; j < exprItems.size(); j++) {
AstNodeExpr* const lhsp = exprItems[i]->cloneTree(false);
AstNodeExpr* const rhsp = exprItems[j]->cloneTree(false);
AstNeq* const neqp = new AstNeq{fl, lhsp, rhsp};
neqp->user1(true); // Mark as depending on rand variable
AstConstraintExpr* const cexprp = new AstConstraintExpr{fl, neqp};
uniquep->addNextHere(cexprp);
}
}
if (!hasArrayVarRef) {
// All items expanded — remove the AstConstraintUnique
uniquep->unlinkFrBack();
VL_DO_DANGLING(uniquep->deleteTree(), uniquep);
}
// If hasArrayVarRef: keep AstConstraintUnique for whole-array handling
}
itemp = nextp;
}
}
void createRandomGenerator(AstClass* const classp) {
if (classp->user3p()) return;
if (classp->extendsp()) {
@ -3175,6 +3230,7 @@ class RandomizeVisitor final : public VNVisitor {
resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt());
}
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars};
if (constrp->itemsp()) {
@ -3445,6 +3501,7 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
expandUniqueElementList(capturedTreep);
ConstraintExprVisitor{nullptr, m_memberMap, capturedTreep, randomizeFuncp,
stdrand, nullptr, m_writtenVars};
}
@ -3583,6 +3640,7 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
expandUniqueElementList(capturedTreep);
ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp,
localGenp, randModeVarp, m_writtenVars};
}

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

View File

@ -0,0 +1,79 @@
// 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);
// verilog_format: on
// Test: unique constraint on explicit array element subset
// IEEE 1800-2017 Section 18.5.9
//
// Uses bit [3:0] (16 possible values) with 5-element unique subsets
// so that without proper constraint enforcement, collisions are
// virtually guaranteed across 20 iterations.
class UniqueElemSubset;
rand bit [3:0] arr[10];
// Unique applied to a subset of array indices
constraint unique_subset_con {
unique { arr[2], arr[3], arr[4], arr[5], arr[6] };
}
function new();
endfunction
function bit check_unique();
for (int i = 2; i <= 6; i++)
for (int j = i + 1; j <= 6; j++)
if (arr[i] == arr[j]) return 0;
return 1;
endfunction
endclass
class UniqueElemFour;
rand bit [3:0] data[8];
// Unique on 4 explicit elements
constraint unique_data_con {
unique { data[1], data[2], data[3], data[4] };
}
function new();
endfunction
function bit check_unique();
for (int i = 1; i <= 4; i++)
for (int j = i + 1; j <= 4; j++)
if (data[i] == data[j]) return 0;
return 1;
endfunction
endclass
module t;
UniqueElemSubset ues;
UniqueElemFour uef;
initial begin
// Test 1: unique on 5 explicit array elements
ues = new();
repeat (20) begin
`checkd(ues.randomize(), 1)
`checkd(ues.check_unique(), 1)
end
// Test 2: unique on 4 explicit array elements
uef = new();
repeat (20) begin
`checkd(uef.randomize(), 1)
`checkd(uef.check_unique(), 1)
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule