Support dynamic array .size in inline randomize() with constraints (#7258) (#7266)

This commit is contained in:
Yilou Wang 2026-03-16 23:48:36 +01:00 committed by GitHub
parent be0f4a507e
commit d57324e5fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 184 additions and 18 deletions

View File

@ -3572,6 +3572,32 @@ class RandomizeVisitor final : public VNVisitor {
UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef");
return new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), Class), varp, access};
}
// Get or create a size variable for a constrained dynamic/queue/assoc array.
// Returns the size variable. Sets wasCreated=true if a new variable was made.
AstVar* createOrGetSizeVar(AstClass* const classp, AstVar* const arrVarp, FileLine* const fl,
AstNodeDType* const signed32DTypep, bool& wasCreated) {
AstVar* sizeVarp = VN_CAST(arrVarp->user4p(), Var);
wasCreated = false;
if (!sizeVarp) {
sizeVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__V" + arrVarp->name() + "_size",
signed32DTypep};
classp->addMembersp(sizeVarp);
m_memberMap.insert(classp, sizeVarp);
sizeVarp->user2p(classp);
arrVarp->user4p(sizeVarp);
wasCreated = true;
}
return sizeVarp;
}
// 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}};
sizeGtep->user1(true);
return new AstConstraintExpr{fl, sizeGtep};
}
AstNodeExpr* getFromp(AstNodeExpr* const exprp) {
if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) {
return memberSelp->fromp();
@ -4719,6 +4745,57 @@ class RandomizeVisitor final : public VNVisitor {
// Generate constraint setup code and a hardcoded call to the solver
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
// Pre-scan captured tree for .size on dynamic/assoc arrays and replace
// with size variables, using shared createOrGetSizeVar helper.
// After CaptureVisitor, fromp() of .size is AstMemberSel (not VarRef),
// so extract the underlying variable from the MemberSel chain.
// Collect resize statements to add after solver call, guarded by success.
AstNode* inlineResizeStmtsp = nullptr;
{
std::vector<AstCMethodHard*> sizeMethodps;
capturedTreep->foreachAndNext([&](AstCMethodHard* methodp) {
if (methodp->method() == VCMethod::DYN_SIZE
|| methodp->method() == VCMethod::ASSOC_SIZE) {
sizeMethodps.push_back(methodp);
}
});
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)) {
arrVarp = memberSelp->varp();
}
if (!arrVarp) continue;
// Only handle rand-declared dynamic/assoc array variables
if (!arrVarp->rand().isRandomizable()) continue;
FileLine* const fl = methodp->fileline();
bool wasCreated = false;
AstVar* const sizeVarp = createOrGetSizeVar(
classp, arrVarp, fl, methodp->findSigned32DType(), wasCreated);
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, classp, arrVarp, VAccess::READWRITE},
VCMethod::DYN_RESIZE, new AstVarRef{fl, sizeVarp, VAccess::READ}};
resizep->dtypep(methodp->findVoidDType());
inlineResizeStmtsp
= AstNode::addNext(inlineResizeStmtsp, new AstStmtExpr{fl, resizep});
}
// Append size >= 0 constraint so ConstraintExprVisitor processes it
capturedTreep->addNext(createSizeGteZeroConstraint(fl, sizeVarp));
}
AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ};
sizeVarRefp->user1(true);
methodp->replaceWith(sizeVarRefp);
VL_DO_DANGLING(methodp->deleteTree(), methodp);
}
}
{
expandUniqueElementList(capturedTreep);
ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp,
@ -4735,6 +4812,16 @@ class RandomizeVisitor final : public VNVisitor {
new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE},
new AstAnd{nodep->fileline(), basicRandomizeFuncCallp, solverCallp}});
// Resize constrained arrays only when solver succeeds, mirroring
// the class-level if(__Vsize_ok) guard pattern
if (inlineResizeStmtsp) {
AstVar* const retVarp = VN_AS(randomizeFuncp->fvarp(), Var);
AstIf* const ifp = new AstIf{nodep->fileline(),
new AstVarRef{nodep->fileline(), retVarp, VAccess::READ},
inlineResizeStmtsp, nullptr};
randomizeFuncp->addStmtsp(ifp);
}
// Call nested post_randomize on rand class-type members (IEEE 18.4.1)
if (classHasRandClassMembers(classp)) {
AstTask* const postTaskp = getCreateNestedCallbackTask(classp, "post");
@ -4774,16 +4861,10 @@ class RandomizeVisitor final : public VNVisitor {
return;
}
AstVar* const queueVarp = queueVarRefp->varp();
AstVar* sizeVarp = VN_CAST(queueVarp->user4p(), Var);
if (!sizeVarp) {
sizeVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__V" + queueVarp->name() + "_size",
nodep->findSigned32DType()};
classp->addMembersp(sizeVarp);
m_memberMap.insert(classp, sizeVarp);
sizeVarp->user2p(classp);
queueVarp->user4p(sizeVarp);
bool wasCreated = false;
AstVar* const sizeVarp = createOrGetSizeVar(classp, queueVarp, fl,
nodep->findSigned32DType(), wasCreated);
if (wasCreated) {
// Associative arrays have no resize(); only generate resize
// for dynamic arrays and queues
if (!VN_IS(queueVarp->dtypep()->skipRefp(), AssocArrayDType)) {
@ -4802,11 +4883,7 @@ class RandomizeVisitor final : public VNVisitor {
// Since size variable is signed int, we need additional constraint
// to make sure it is always >= 0.
AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ};
sizeVarRefp->user1(true);
AstGteS* const sizeGtep = new AstGteS{fl, sizeVarRefp, new AstConst{fl, 0}};
sizeGtep->user1(true);
m_constraintp->addItemsp(new AstConstraintExpr{fl, sizeGtep});
m_constraintp->addItemsp(createSizeGteZeroConstraint(fl, sizeVarp));
}
AstVarRef* const sizeVarRefp = new AstVarRef{fl, sizeVarp, VAccess::READ};
sizeVarRefp->user1(true);

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,71 @@
// 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 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);
// verilog_format: on
class Packet;
rand int unsigned m_length;
rand byte m_data[];
rand byte m_mask[];
constraint c_length { m_length inside {[1:16]}; }
endclass
class MultiArray;
rand int unsigned m_len_a;
rand int unsigned m_len_b;
rand int m_arr_a[];
rand int m_arr_b[];
constraint c_lens {
m_len_a inside {[2:8]};
m_len_b inside {[3:10]};
}
endclass
module t;
initial begin
automatic Packet pkt = new;
automatic MultiArray ma = new;
automatic int ok;
// Scenario 1: single class with two dynamic arrays sized by one field
repeat (5) begin
ok = pkt.randomize() with {
m_data.size == m_length;
m_mask.size == m_length;
foreach (m_mask[i]) m_mask[i] inside {8'h00, 8'hFF};
};
`checkh(ok, 1)
`checkh(pkt.m_data.size(), pkt.m_length)
`checkh(pkt.m_mask.size(), pkt.m_length)
// Verify mask values are constrained
for (int i = 0; i < pkt.m_mask.size(); i++) begin
if (pkt.m_mask[i] !== 8'h00 && pkt.m_mask[i] !== 8'hFF) begin
$write("%%Error: m_mask[%0d]=%0h not in {00, FF}\n", i, pkt.m_mask[i]);
`stop;
end
end
end
// Scenario 2: two arrays with independent size constraints
repeat (5) begin
ok = ma.randomize() with {
m_arr_a.size == m_len_a;
m_arr_b.size == m_len_b;
};
`checkh(ok, 1)
`checkh(ma.m_arr_a.size(), ma.m_len_a)
`checkh(ma.m_arr_b.size(), ma.m_len_b)
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -8,7 +8,4 @@
15 | rand Cls cls;
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%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