diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 158aa873e..2d99a4b2a 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -40,6 +40,7 @@ #include "V3FileLine.h" #include "V3Global.h" #include "V3MemberMap.h" +#include "V3Task.h" #include "V3UniqueNames.h" #include @@ -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( diff --git a/test_regress/t/t_constraint_func_call.py b/test_regress/t/t_constraint_func_call.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_func_call.py @@ -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() diff --git a/test_regress/t/t_constraint_func_call.v b/test_regress/t/t_constraint_func_call.v new file mode 100644 index 000000000..5993834f2 --- /dev/null +++ b/test_regress/t/t_constraint_func_call.v @@ -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 diff --git a/test_regress/t/t_constraint_func_call_unsup.out b/test_regress/t/t_constraint_func_call_unsup.out new file mode 100644 index 000000000..8d4731ae7 --- /dev/null +++ b/test_regress/t/t_constraint_func_call_unsup.out @@ -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 diff --git a/test_regress/t/t_constraint_func_call_unsup.py b/test_regress/t/t_constraint_func_call_unsup.py new file mode 100755 index 000000000..18ef27714 --- /dev/null +++ b/test_regress/t/t_constraint_func_call_unsup.py @@ -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() diff --git a/test_regress/t/t_constraint_func_call_unsup.v b/test_regress/t/t_constraint_func_call_unsup.v new file mode 100644 index 000000000..450f0fbe8 --- /dev/null +++ b/test_regress/t/t_constraint_func_call_unsup.v @@ -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