Support function calls with random arguments in constraints (#7061) (#7083)

This commit is contained in:
Yilou Wang 2026-02-19 11:07:55 +01:00 committed by GitHub
parent f843780aad
commit 0e26b049ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 233 additions and 1 deletions

View File

@ -40,6 +40,7 @@
#include "V3FileLine.h"
#include "V3Global.h"
#include "V3MemberMap.h"
#include "V3Task.h"
#include "V3UniqueNames.h"
#include <queue>
@ -547,7 +548,16 @@ class RandomizeMarkVisitor final : public VNVisitor {
return;
}
if (nodep->name() != "randomize") return;
if (nodep->name() != "randomize") {
// Propagate user1 from children (same pattern as visit(AstNodeExpr*))
if (m_constraintExprGenp || m_inStdWith) {
nodep->user1((nodep->op1p() && nodep->op1p()->user1())
|| (nodep->op2p() && nodep->op2p()->user1())
|| (nodep->op3p() && nodep->op3p()->user1())
|| (nodep->op4p() && nodep->op4p()->user1()));
}
return;
}
AstClass* classp = m_classp;
if (const AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall)) {
if (const AstClassRefDType* const classRefp
@ -862,6 +872,55 @@ class ConstraintExprVisitor final : public VNVisitor {
return selp;
}
// Extract return expression from a simple function body, or nullptr if too complex.
// Uses foreach to walk all assigns regardless of body organization (JumpBlock nesting etc.).
// Takes the last assignment to the return variable -- the first is the initializer.
AstNodeExpr* extractReturnExpr(AstFunc* funcp) {
AstVar* const retVarp = VN_CAST(funcp->fvarp(), Var);
if (!retVarp) return nullptr;
AstNodeExpr* retExprp = nullptr;
int assignCount = 0;
funcp->foreach([&](AstAssign* assignp) {
AstVarRef* const lhsp = VN_CAST(assignp->lhsp(), VarRef);
if (!lhsp || lhsp->varp() != retVarp) return;
retExprp = assignp->rhsp();
++assignCount;
});
// Simple function: initializer + return (2) or just return (1)
if (assignCount > 2 || !retExprp) return nullptr;
return retExprp;
}
// Bottom-up user1 propagation on inlined expression tree
void propagateUser1InlineRecurse(AstNodeExpr* nodep) {
if (VN_IS(nodep, NodeVarRef)) {
nodep->user1(VN_AS(nodep, NodeVarRef)->varp()->rand().isRandomizable());
return;
}
if (VN_IS(nodep, Const)) {
nodep->user1(false);
return;
}
bool anyChild = false;
if (AstNodeExpr* const cp = VN_CAST(nodep->op1p(), NodeExpr)) {
propagateUser1InlineRecurse(cp);
anyChild |= cp->user1();
}
if (AstNodeExpr* const cp = VN_CAST(nodep->op2p(), NodeExpr)) {
propagateUser1InlineRecurse(cp);
anyChild |= cp->user1();
}
if (AstNodeExpr* const cp = VN_CAST(nodep->op3p(), NodeExpr)) {
propagateUser1InlineRecurse(cp);
anyChild |= cp->user1();
}
if (AstNodeExpr* const cp = VN_CAST(nodep->op4p(), NodeExpr)) {
propagateUser1InlineRecurse(cp);
anyChild |= cp->user1();
}
nodep->user1(anyChild);
}
// VISITORS
void visit(AstNodeVarRef* nodep) override {
AstVar* varp = nodep->varp();
@ -1892,6 +1951,50 @@ class ConstraintExprVisitor final : public VNVisitor {
if (editFormat(nodep)) return;
nodep->v3fatalSrc("Method not handled in constraints? " << nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
if (editFormat(nodep)) return;
AstFunc* const funcp = VN_CAST(nodep->taskp(), Func);
if (!funcp) {
// Tasks have no return value and can't appear in expressions.
// Parser rejects this, so reaching here indicates a compiler bug.
nodep->v3fatalSrc("Unexpected task call in constraint expression");
return;
}
AstNodeExpr* const retExprp = extractReturnExpr(funcp);
if (!retExprp) {
nodep->v3warn(CONSTRAINTIGN,
"Unsupported: complex function in constraint, treating as state");
nodep->user1(false);
if (editFormat(nodep)) return;
return;
}
// Map formal parameters to actual arguments using V3Task infrastructure
const V3TaskConnects tconnects
= V3Task::taskConnects(nodep, funcp->stmtsp(), nullptr, false);
// Clone return expression, substitute params with args
AstNodeExpr* const inlinedp = retExprp->cloneTreePure(false);
for (const auto& tconnect : tconnects) {
const AstVar* const portp = tconnect.first;
AstArg* const argp = tconnect.second;
if (!argp || !argp->exprp()) continue;
inlinedp->foreach([&](AstVarRef* refp) {
if (refp->varp() == portp) {
refp->replaceWith(argp->exprp()->cloneTreePure(false));
VL_DO_DANGLING(refp->deleteTree(), refp);
}
});
}
inlinedp->dtypep(nodep->dtypep());
propagateUser1InlineRecurse(inlinedp);
nodep->replaceWith(inlinedp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
iterate(inlinedp);
}
void visit(AstNodeExpr* nodep) override {
if (editFormat(nodep)) return;
nodep->v3fatalSrc(

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,58 @@
// 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);
`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 FuncConstraintTest;
rand bit [7:0] value;
rand bit [7:0] mask;
function bit [7:0] get_min_value(bit [7:0] m);
return m & 8'hF0;
endfunction
function bit [7:0] get_max_value(bit [7:0] m);
return m | 8'h0F;
endfunction
constraint func_con {
mask inside {[8'h10:8'hF0]};
value >= get_min_value(mask);
value <= get_max_value(mask);
}
function new();
endfunction
endclass
module t;
FuncConstraintTest fct;
int rand_ok;
bit [7:0] min_val;
bit [7:0] max_val;
initial begin
fct = new();
repeat (10) begin
rand_ok = fct.randomize();
`checkd(rand_ok, 1)
`check_range(fct.mask, 8'h10, 8'hF0)
min_val = fct.get_min_value(fct.mask);
max_val = fct.get_max_value(fct.mask);
`check_range(fct.value, min_val, max_val)
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,6 @@
%Warning-CONSTRAINTIGN: t/t_constraint_func_call_unsup.v:18:23: Unsupported: complex function in constraint, treating as state
18 | constraint c { x <= complex_func(y); }
| ^~~~~~~~~~~~
... 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.
%Error: Exiting due to

View File

@ -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('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,28 @@
// 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
class Cls;
rand bit [7:0] x;
rand bit [7:0] y;
function bit [7:0] complex_func(bit [7:0] m);
if (m > 128)
return m;
else
return m + 1;
endfunction
constraint c { x <= complex_func(y); }
endclass
module t;
Cls obj;
initial begin
obj = new;
void'(obj.randomize());
end
endmodule