Support constraints on fixed-size array of class object members (#7170) (#7183)

This commit is contained in:
Yilou Wang 2026-03-03 12:27:31 +01:00 committed by GitHub
parent ee68d1c70c
commit ed84f3adb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 224 additions and 36 deletions

View File

@ -1021,8 +1021,8 @@ class ConstraintExprVisitor final : public VNVisitor {
// For global constraints, delete nodep after processing
if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep);
// Detect if variable is an array of class references
bool isClassRefArray = false;
bool isUnpackedClassRefArray = false;
AstClassRefDType* elemClassRefDtp = nullptr;
{
AstNodeDType* varDtp = varp->dtypep()->skipRefp();
@ -1030,12 +1030,14 @@ class ConstraintExprVisitor final : public VNVisitor {
|| VN_IS(varDtp, UnpackArrayDType) || VN_IS(varDtp, AssocArrayDType)) {
AstNodeDType* const elemDtp = varDtp->subDTypep()->skipRefp();
elemClassRefDtp = VN_CAST(elemDtp, ClassRefDType);
if (elemClassRefDtp) isClassRefArray = true;
if (elemClassRefDtp) {
isClassRefArray = true;
isUnpackedClassRefArray = VN_IS(varDtp, UnpackArrayDType);
}
}
}
if (isClassRefArray && !membersel) {
// Per-member registration loop for class ref arrays
FileLine* const fl = varp->fileline();
AstClass* const elemClassp = elemClassRefDtp->classp();
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
@ -1049,22 +1051,36 @@ class ConstraintExprVisitor final : public VNVisitor {
stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE},
new AstConst{fl, 0}});
AstVarRef* const arraySizeRef = new AstVarRef{fl, varClassp, varp, VAccess::READ};
arraySizeRef->classOrPackagep(classOrPackagep);
AstCMethodHard* const sizep
= new AstCMethodHard{fl, arraySizeRef, VCMethod::DYN_SIZE, nullptr};
sizep->dtypeSetUInt32();
AstNodeExpr* sizep;
if (isUnpackedClassRefArray) {
const int arraySize
= VN_AS(varp->dtypep()->skipRefp(), UnpackArrayDType)->elementsConst();
sizep = new AstConst{fl, static_cast<uint32_t>(arraySize)};
} else {
AstVarRef* const arraySizeRef
= new AstVarRef{fl, varClassp, varp, VAccess::READ};
arraySizeRef->classOrPackagep(classOrPackagep);
AstCMethodHard* const dynSizep
= new AstCMethodHard{fl, arraySizeRef, VCMethod::DYN_SIZE, nullptr};
dynSizep->dtypeSetUInt32();
sizep = dynSizep;
}
AstLoop* const loopp = new AstLoop{fl};
stmtsp->addNext(loopp);
loopp->addStmtsp(new AstLoopTest{
fl, loopp, new AstLt{fl, new AstVarRef{fl, iterVarp, VAccess::READ}, sizep}});
AstVarRef* const arrayAtRef = new AstVarRef{fl, varClassp, varp, VAccess::READ};
arrayAtRef->classOrPackagep(classOrPackagep);
AstCMethodHard* const atReadp
= new AstCMethodHard{fl, arrayAtRef, VCMethod::ARRAY_AT,
new AstVarRef{fl, iterVarp, VAccess::READ}};
AstVarRef* const arrayReadRef = new AstVarRef{fl, varClassp, varp, VAccess::READ};
arrayReadRef->classOrPackagep(classOrPackagep);
AstNodeExpr* atReadp;
if (isUnpackedClassRefArray) {
atReadp = new AstArraySel{fl, arrayReadRef,
new AstVarRef{fl, iterVarp, VAccess::READ}};
} else {
atReadp = new AstCMethodHard{fl, arrayReadRef, VCMethod::ARRAY_AT,
new AstVarRef{fl, iterVarp, VAccess::READ}};
}
atReadp->dtypep(elemClassRefDtp);
AstIf* const ifNonNullp = new AstIf{
fl, new AstNeq{fl, atReadp, new AstConst{fl, AstConst::Null{}}}, nullptr};
@ -1090,9 +1106,14 @@ class ConstraintExprVisitor final : public VNVisitor {
const int memberWidth = memberDtp->width();
AstCStmt* const fmtp = new AstCStmt{fl};
fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%0"
+ std::to_string(fmtWidth) + "x." + memberVarp->name()
+ "\", (unsigned)");
if (isUnpackedClassRefArray) {
fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%x."
+ memberVarp->name() + "\", (unsigned)");
} else {
fmtp->add("VL_SNPRINTF(__Vn, sizeof(__Vn), \"" + smtName + ".%0"
+ std::to_string(fmtWidth) + "x." + memberVarp->name()
+ "\", (unsigned)");
}
fmtp->add(new AstVarRef{fl, iterVarp, VAccess::READ});
fmtp->add(");\n");
ifNonNullp->addThensp(fmtp);
@ -1100,9 +1121,15 @@ class ConstraintExprVisitor final : public VNVisitor {
AstVarRef* const arrayWrRef
= new AstVarRef{fl, varClassp, varp, VAccess::WRITE};
arrayWrRef->classOrPackagep(classOrPackagep);
AstCMethodHard* const atWritep
= new AstCMethodHard{fl, arrayWrRef, VCMethod::ARRAY_AT_WRITE,
new AstVarRef{fl, iterVarp, VAccess::READ}};
AstNodeExpr* atWritep;
if (isUnpackedClassRefArray) {
atWritep = new AstArraySel{fl, arrayWrRef,
new AstVarRef{fl, iterVarp, VAccess::READ}};
} else {
atWritep
= new AstCMethodHard{fl, arrayWrRef, VCMethod::ARRAY_AT_WRITE,
new AstVarRef{fl, iterVarp, VAccess::READ}};
}
atWritep->dtypep(elemClassRefDtp);
AstMemberSel* const memberSelp
= new AstMemberSel{fl, atWritep, memberVarp};
@ -1635,10 +1662,46 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNode* rootNode = nodep->fromp();
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
rootNode = selp->fromp();
// Detect array/assoc array access in global constraints
if (VN_IS(rootNode, ArraySel) || VN_IS(rootNode, AssocSel)) {
if (AstArraySel* const arraySelp = VN_CAST(rootNode, ArraySel)) {
AstNodeDType* const arrayDtp = arraySelp->fromp()->dtypep()->skipRefp();
AstNodeDType* const elemDtp
= arrayDtp->subDTypep() ? arrayDtp->subDTypep()->skipRefp() : nullptr;
if (elemDtp && VN_IS(elemDtp, ClassRefDType)) {
// Nested class ref arrays not yet supported
const bool isSimple
= nodep->fromp() == rootNode && VN_IS(arraySelp->fromp(), VarRef);
if (!isSimple) {
nodep->v3warn(
E_UNSUPPORTED,
"Unsupported: Nested array element access in global constraint");
return;
}
VL_RESTORER(m_structSel);
m_structSel = true;
nodep->user1(true);
arraySelp->user1(true);
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstSFormatF* newp = nullptr;
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
if (fromp->name() == "%@.%@") {
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
fromp->exprsp()->cloneTreePure(true)};
} else {
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
nullptr};
}
} else {
newp = new AstSFormatF{fl, nodep->name(), false, nullptr};
}
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
if (VN_IS(rootNode, AssocSel) || VN_IS(rootNode, ArraySel)) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Array element access in global constraint ");
"Unsupported: Array element access in global constraint");
}
// Check if the root variable participates in global constraints
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {

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,104 @@
// 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
// Test: constraints on class object array element members (fixed-size arrays)
// 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);
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=[%0d:%0d]\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
// verilog_format: on
/* verilator lint_off WIDTHTRUNC */
/* verilator lint_off IMPLICITSTATIC */
class item_t;
rand bit [7:0] value;
endclass
// Scenario A: foreach with range constraint
class container_a;
rand item_t items[4];
constraint val_c {
foreach (items[i]) {
items[i].value inside {[10:200]};
}
}
function new();
foreach (items[i]) items[i] = new;
endfunction
endclass
// Scenario B: foreach with ordering constraint
class container_b;
rand item_t items[4];
constraint order_c {
foreach (items[i]) {
if (i != 0) {
items[i].value > items[i-1].value;
}
}
}
function new();
foreach (items[i]) items[i] = new;
endfunction
endclass
// Scenario C: constant-index constraint (no foreach)
class container_c;
rand item_t items[4];
constraint val_c {
items[0].value < items[1].value;
items[1].value < items[2].value;
items[2].value < items[3].value;
}
function new();
foreach (items[i]) items[i] = new;
endfunction
endclass
module t;
initial begin
automatic container_a ca = new;
automatic container_b cb = new;
automatic container_c cc = new;
automatic int ok;
repeat (20) begin
// Scenario A: foreach range
ok = ca.randomize();
`checkd(ok, 1);
for (int i = 0; i < 4; i++) begin
`check_range(ca.items[i].value, 10, 200);
end
// Scenario B: foreach ordering
ok = cb.randomize();
`checkd(ok, 1);
for (int i = 1; i < 4; i++) begin
if (cb.items[i].value <= cb.items[i-1].value) begin
$write("%%Error: %s:%0d: ordering violated: items[%0d]=%0d <= items[%0d]=%0d\n",
`__FILE__, `__LINE__, i, cb.items[i].value, i-1, cb.items[i-1].value);
`stop;
end
end
// Scenario C: constant-index ordering
ok = cc.randomize();
`checkd(ok, 1);
for (int i = 1; i < 4; i++) begin
if (cc.items[i].value <= cc.items[i-1].value) begin
$write("%%Error: %s:%0d: ordering violated: items[%0d]=%0d <= items[%0d]=%0d\n",
`__FILE__, `__LINE__, i, cc.items[i].value, i-1, cc.items[i-1].value);
`stop;
end
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,26 +1,20 @@
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Array element access in global constraint
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Nested array element access in global constraint
41 | m_mid.m_arr[0].m_x == 200;
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Array element access in global constraint
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Nested array element access in global constraint
42 | m_mid.m_arr[0].m_y == 201;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Array element access in global constraint
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Nested array element access in global constraint
47 | m_mid_arr[0].m_obj.m_x == 300;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: Array element access in global constraint
47 | m_mid_arr[0].m_obj.m_x == 300;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Array element access in global constraint
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Nested array element access in global constraint
48 | m_mid_arr[0].m_obj.m_y == 301;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: Array element access in global constraint
48 | m_mid_arr[0].m_obj.m_y == 301;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Array element access in global constraint
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Nested array element access in global constraint
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: Array element access in global constraint
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:59:16: Unsupported: Array element access in global constraint
59 | m_assoc[0].m_x == 500;
| ^~~
%Error: Exiting due to

View File

@ -52,6 +52,12 @@ class Outer;
constraint c_multi_array {
m_mid_arr[1].m_arr[2].m_y == 400;
}
// Case 5: Associative array element member access
rand Inner m_assoc[int];
constraint c_assoc {
m_assoc[0].m_x == 500;
}
endclass
module t_constraint_global_arr_unsup;