Fix array size references in constraints (#8040)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
This commit is contained in:
parent
351d67c81e
commit
3fa88ae7f3
|
|
@ -2880,6 +2880,21 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
return;
|
||||
}
|
||||
|
||||
// We've handled DYN_SIZE already if they should be randomized
|
||||
if (nodep->method() == VCMethod::DYN_SIZE || nodep->method() == VCMethod::ASSOC_SIZE) {
|
||||
AstNodeExpr* nodeExprp = nodep->fromp();
|
||||
// Check if it is a pure chain of MemberSel ended with VarRef
|
||||
// if not throw warning
|
||||
while (VN_IS(nodeExprp, MemberSel)) {
|
||||
nodeExprp = VN_AS(nodeExprp, MemberSel)->fromp();
|
||||
}
|
||||
if (VN_IS(nodeExprp, VarRef)) {
|
||||
nodep->replaceWith(getConstFormat(nodep->cloneTreePure(false)));
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nodep->v3warn(CONSTRAINTIGN,
|
||||
"Unsupported: randomizing this expression, treating as state");
|
||||
nodep->user1(false);
|
||||
|
|
@ -3310,6 +3325,7 @@ public:
|
|||
// PUBLIC METHODS
|
||||
|
||||
AstArg* getArgs() const { return m_argsp; }
|
||||
AstVar* getThisp() const { return m_thisp; }
|
||||
|
||||
void addFunctionArguments(AstNodeFTask* funcp) const {
|
||||
for (AstArg* argp = getArgs(); argp; argp = VN_AS(argp->nextp(), Arg)) {
|
||||
|
|
@ -4324,10 +4340,16 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
}
|
||||
// Build a size >= 0 constraint expression (signed int size must be non-negative).
|
||||
// Returns a new AstConstraintExpr with user1 bits set for solver visibility.
|
||||
AstConstraintExpr* createSizeGteZeroConstraint(FileLine* const fl, AstVar* const sizeVarp) {
|
||||
AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ};
|
||||
sizeVarRefp->user1(true);
|
||||
AstGteS* const sizeGtep = new AstGteS{fl, sizeVarRefp, new AstConst{fl, 0}};
|
||||
AstConstraintExpr* createSizeGteZeroConstraint(FileLine* const fl, AstVar* const sizeVarp,
|
||||
AstNodeExpr* derefFromp = nullptr) {
|
||||
AstNodeExpr* sizeNodeExprp = nullptr;
|
||||
if (derefFromp) {
|
||||
sizeNodeExprp = buildMemberSelExprp(derefFromp, sizeVarp);
|
||||
} else {
|
||||
sizeNodeExprp = new AstVarRef{fl, sizeVarp, VAccess::READ};
|
||||
}
|
||||
sizeNodeExprp->user1(true);
|
||||
AstGteS* const sizeGtep = new AstGteS{fl, sizeNodeExprp, new AstConst{fl, 0}};
|
||||
sizeGtep->user1(true);
|
||||
return new AstConstraintExpr{fl, sizeGtep};
|
||||
}
|
||||
|
|
@ -4402,6 +4424,27 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
}
|
||||
return commonp;
|
||||
}
|
||||
// Returns MemberSel chain, with deepest VarRef being reference to deepestVarp
|
||||
AstNodeExpr* buildMemberSelExprp(AstNodeExpr* exprp, AstVar* deepestVarp) {
|
||||
UASSERT_OBJ(VN_IS(exprp, MemberSel) || VN_IS(exprp, VarRef), exprp,
|
||||
"Should be MemberSel or VarRef");
|
||||
|
||||
AstNodeExpr* resExprp = nullptr;
|
||||
if (const AstMemberSel* memberSelp = VN_CAST(exprp, MemberSel)) {
|
||||
resExprp = buildMemberSelExprp(memberSelp->fromp(), nullptr);
|
||||
AstVar* varp = memberSelp->varp();
|
||||
if (deepestVarp) varp = deepestVarp;
|
||||
resExprp = new AstMemberSel{exprp->fileline(), resExprp, varp};
|
||||
} else if (const AstVarRef* varRefp = VN_CAST(exprp, VarRef)) {
|
||||
AstVar* varp = varRefp->varp();
|
||||
if (deepestVarp) varp = deepestVarp;
|
||||
resExprp = new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), NodeModule), varp,
|
||||
VAccess::READWRITE};
|
||||
}
|
||||
|
||||
resExprp->user1(true);
|
||||
return resExprp;
|
||||
}
|
||||
|
||||
void addBasicRandomizeBody(AstFunc* const basicRandomizep, AstClass* const nodep,
|
||||
AstVar* randModeVarp) {
|
||||
|
|
@ -6011,42 +6054,52 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
for (AstCMethodHard* const methodp : sizeMethodps) {
|
||||
// Extract array variable from fromp (VarRef or MemberSel after capture)
|
||||
AstVar* arrVarp = nullptr;
|
||||
if (AstVarRef* const varRefp = VN_CAST(methodp->fromp(), VarRef)) {
|
||||
arrVarp = varRefp->varp();
|
||||
} else if (AstMemberSel* const memberSelp = VN_CAST(methodp->fromp(), MemberSel)) {
|
||||
AstNodeExpr* const rootExprp = methodp->fromp();
|
||||
if (AstMemberSel* memberSelp = VN_CAST(rootExprp, MemberSel)) {
|
||||
arrVarp = memberSelp->varp();
|
||||
while (VN_IS(memberSelp->fromp(), MemberSel)) {
|
||||
memberSelp = VN_CAST(memberSelp->fromp(), MemberSel);
|
||||
}
|
||||
// Only variables that are part of object that randomize is called on shall be
|
||||
// randomized (18.6.1), skip randomization if deepest variable is reference to
|
||||
// __Vthis
|
||||
const AstNodeExpr* fromp = memberSelp->fromp();
|
||||
if (const AstVarRef* varRefp = VN_CAST(fromp, VarRef)) {
|
||||
if (varRefp->varp() == captured.getThisp()) continue;
|
||||
} else {
|
||||
fromp->v3warn(E_UNSUPPORTED, "Unsupported: Unsupported size constraint "
|
||||
"inside 'with' statement of type "
|
||||
<< fromp->dtypep()->prettyTypeName());
|
||||
}
|
||||
} else if (AstVarRef* const varRefp = VN_CAST(rootExprp, VarRef)) {
|
||||
arrVarp = varRefp->varp();
|
||||
}
|
||||
if (!arrVarp) continue;
|
||||
// Only handle rand-declared dynamic/assoc array variables
|
||||
if (!arrVarp->rand().isRandomizable()) continue;
|
||||
// Do not randomize associative array size (IEEE 18.4)
|
||||
if (VN_IS(arrVarp->dtypep()->skipRefp(), AssocArrayDType)) continue;
|
||||
FileLine* const fl = methodp->fileline();
|
||||
bool wasCreated = false;
|
||||
AstVar* const sizeVarp
|
||||
= createOrGetSizeVar(classp, arrVarp, fl, methodp->findIntDType(), wasCreated);
|
||||
AstClass* const varClassp = VN_AS(arrVarp->user2p(), Class);
|
||||
AstVar* const sizeVarp = createOrGetSizeVar(varClassp, arrVarp, fl,
|
||||
methodp->findIntDType(), wasCreated);
|
||||
// arrVarp and sizeVarp may live in a base class when the
|
||||
// array is inherited; route VarRefs through their declaring
|
||||
// class so V3Scope can resolve them.
|
||||
AstNodeModule* const arrClassp = VN_AS(arrVarp->user2p(), NodeModule);
|
||||
AstNodeModule* const sizeClassp = VN_AS(sizeVarp->user2p(), NodeModule);
|
||||
if (wasCreated) {
|
||||
// Generate resize for dynamic arrays/queues (not assoc arrays)
|
||||
if (!VN_IS(arrVarp->dtypep()->skipRefp(), AssocArrayDType)) {
|
||||
AstCMethodHard* const resizep = new AstCMethodHard{
|
||||
fl, new AstVarRef{fl, arrClassp, arrVarp, VAccess::READWRITE},
|
||||
VCMethod::DYN_RESIZE,
|
||||
new AstVarRef{fl, sizeClassp, sizeVarp, VAccess::READ}};
|
||||
resizep->dtypep(methodp->findVoidDType());
|
||||
inlineResizeStmtsp
|
||||
= AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep});
|
||||
}
|
||||
|
||||
AstNodeExpr* const resizeVarRefp = buildMemberSelExprp(rootExprp, arrVarp);
|
||||
AstNodeExpr* const sizeExprp = buildMemberSelExprp(rootExprp, sizeVarp);
|
||||
// Append size >= 0 constraint so ConstraintExprVisitor processes it
|
||||
capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp));
|
||||
capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp, rootExprp));
|
||||
AstCMethodHard* const resizep
|
||||
= new AstCMethodHard{fl, resizeVarRefp, VCMethod::DYN_RESIZE, sizeExprp};
|
||||
resizep->dtypep(methodp->findVoidDType());
|
||||
inlineResizeStmtsp
|
||||
= AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep});
|
||||
}
|
||||
AstVarRef* const sizeVarRefp
|
||||
= new AstVarRef{fl, sizeClassp, sizeVarp, VAccess::READ};
|
||||
sizeVarRefp->user1(true);
|
||||
methodp->replaceWith(sizeVarRefp);
|
||||
AstNodeExpr* const sizeNodeExprp = buildMemberSelExprp(rootExprp, sizeVarp);
|
||||
methodp->replaceWith(sizeNodeExprp);
|
||||
VL_DO_DANGLING(methodp->deleteTree(), methodp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
%Error-UNSUPPORTED: t/t_constraint_arr_with_arrsize_unsup.v:27:14: Unsupported: Unsupported size constraint inside 'with' statement of type CLASSREFDTYPE 'Sub'
|
||||
: ... note: In instance 't'
|
||||
27 | if (i.s[0].arr.size > 0) {
|
||||
| ^
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error: Internal Error: t/t_constraint_arr_with_arrsize_unsup.v:27:14: ../V3Randomize.cpp:#: Should be MemberSel or VarRef
|
||||
: ... note: In instance 't'
|
||||
27 | if (i.s[0].arr.size > 0) {
|
||||
| ^
|
||||
... This fatal error may be caused by the earlier error(s); resolve those first.
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/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('linter')
|
||||
|
||||
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class Sub;
|
||||
rand int arr[];
|
||||
|
||||
function new();
|
||||
arr = new[10];
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class Indep;
|
||||
rand int val;
|
||||
Sub s[];
|
||||
|
||||
function new();
|
||||
s = new [10];
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class Cls;
|
||||
function int randomize_gpr(Indep i);
|
||||
return (i.randomize() with {
|
||||
if (i.s[0].arr.size > 0) {
|
||||
i.val inside {i.s[0].arr};
|
||||
}
|
||||
});
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
Cls c;
|
||||
Indep i;
|
||||
|
||||
initial begin
|
||||
c = new;
|
||||
i = new;
|
||||
|
||||
c.randomize_gpr(i);
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
/*verilator lint_off*/
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\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);
|
||||
/*verilator lint_on*/
|
||||
|
||||
class SubSubClass;
|
||||
rand int subVal;
|
||||
rand int subArr[];
|
||||
|
||||
constraint c{
|
||||
subArr.size < 10;
|
||||
}
|
||||
endclass
|
||||
|
||||
class SubClass;
|
||||
rand int subVal;
|
||||
rand int subArr[];
|
||||
rand int assocArr[string] = '{ "test": 1, "test2" : 2 };
|
||||
SubSubClass ssc;
|
||||
|
||||
function new();
|
||||
ssc = new;
|
||||
ssc.randomize();
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class IndepClass;
|
||||
rand int val0;
|
||||
rand int val1;
|
||||
rand int val2;
|
||||
rand SubClass sc;
|
||||
|
||||
function new();
|
||||
sc = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class BaseClass;
|
||||
rand int arr[];
|
||||
// Size-constrained array
|
||||
constraint bc {
|
||||
arr.size < 10;
|
||||
arr.size > 5;
|
||||
};
|
||||
endclass
|
||||
|
||||
class ExtClass0 extends BaseClass;
|
||||
function int randomize_gpr(IndepClass cls);
|
||||
return (cls.randomize() with {
|
||||
// If with arr.size on array, that's a field of class
|
||||
// calling randomize() with
|
||||
if (arr.size > 0) {
|
||||
val0 inside {arr};
|
||||
}
|
||||
|
||||
// If with arr.size on array thats a field inside subclass
|
||||
// chain
|
||||
if (cls.sc.ssc.subArr.size > 0 ) {
|
||||
val1 == 'hDEADBEEF;
|
||||
}
|
||||
|
||||
// Randomizable array size
|
||||
sc.subArr.size > 25;
|
||||
sc.subArr.size < 50;
|
||||
|
||||
// If with associative array size
|
||||
if (sc.assocArr.size > 0) {
|
||||
val2 == 'hCAFEBABE;
|
||||
}
|
||||
});
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class ExtClass1 extends BaseClass;
|
||||
// Class that extends BaseClass and uses arr.size variable
|
||||
constraint c {
|
||||
unique{arr};
|
||||
foreach(arr[i]) {
|
||||
arr[i] != 0;
|
||||
}
|
||||
}
|
||||
endclass
|
||||
|
||||
module t;
|
||||
ExtClass0 ext0;
|
||||
ExtClass1 ext1;
|
||||
IndepClass indep;
|
||||
|
||||
initial begin
|
||||
indep = new;
|
||||
ext0 = new;
|
||||
ext1 = new;
|
||||
repeat(10) begin
|
||||
`checkh(ext0.randomize(), 1);
|
||||
`checkh(ext0.randomize(), 1);
|
||||
`checkh(ext0.randomize_gpr(indep), 1);
|
||||
|
||||
`checkh(indep.val0 inside {ext0.arr}, 1);
|
||||
`checkh(indep.val1, 'hDEADBEEF);
|
||||
`checkh(indep.val2, 'hCAFEBABE);
|
||||
|
||||
`check_range(ext0.arr.size(), 5, 10);
|
||||
`check_range(indep.sc.subArr.size(), 25, 50);
|
||||
|
||||
foreach (ext0.arr[i]) begin
|
||||
if (ext0.arr[i] == 0) begin
|
||||
`stop;
|
||||
end
|
||||
foreach (ext0.arr[j]) begin
|
||||
if (i == j) continue;
|
||||
if (ext0.arr[i] == ext0.arr[j]) begin
|
||||
`stop;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish();
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -10,11 +10,30 @@
|
|||
`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
|
||||
|
||||
class ArgCls;
|
||||
rand int b;
|
||||
endclass
|
||||
|
||||
class Cls;
|
||||
rand int m_x;
|
||||
rand int m_z;
|
||||
int y = -1; // class member named 'y' intentionally shadows the caller arg
|
||||
int lo = -100; // class member named 'lo' intentionally shadows the caller arg
|
||||
rand int a[];
|
||||
|
||||
function new();
|
||||
a = new [10];
|
||||
endfunction
|
||||
|
||||
// Randomize array inside class that is argument
|
||||
// with .size inside if
|
||||
function automatic int randomize_subcls(ArgCls ac);
|
||||
return ac.randomize() with {
|
||||
if (a.size() > 0) {
|
||||
ac.b == a.size();
|
||||
}
|
||||
};
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
// 'y' is not in the list -> resolves to the caller arg (10), not class member (-1).
|
||||
|
|
@ -76,8 +95,10 @@ endfunction
|
|||
module t;
|
||||
initial begin
|
||||
Cls c;
|
||||
ArgCls ac;
|
||||
int i;
|
||||
c = new;
|
||||
ac = new;
|
||||
repeat (20) begin
|
||||
i = func_restricted(c, 10);
|
||||
`checkd(i, 1);
|
||||
|
|
@ -98,6 +119,11 @@ module t;
|
|||
`check_range(c.m_x, 1, 7);
|
||||
i = func_sequential(c, 6);
|
||||
`checkd(i, 1);
|
||||
i = c.randomize_subcls(ac);
|
||||
`checkd(i, 1);
|
||||
if (c.a.size() > 0) begin
|
||||
`checkd(ac.b, c.a.size());
|
||||
end
|
||||
// Statement form: discards return value via void'.
|
||||
void'(c.randomize() with (m_x) { m_x > 0; m_x < 5; });
|
||||
`check_range(c.m_x, 1, 4);
|
||||
|
|
|
|||
Loading…
Reference in New Issue