From fc49811fd75aa6825bb990c69971fa78d4785767 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 4 May 2026 17:28:17 +0200 Subject: [PATCH] Support randsequence production function ports (#7522) --- src/V3LinkDot.cpp | 9 +++-- src/V3LinkParse.cpp | 12 +++++-- src/V3RandSequence.cpp | 39 +++++++++++++++------ test_regress/t/t_randsequence_func.out | 9 ----- test_regress/t/t_randsequence_func.py | 5 ++- test_regress/t/t_randsequence_func.v | 39 ++++++++++++++++++++- test_regress/t/t_randsequence_func_bad.out | 23 +++++++++++++ test_regress/t/t_randsequence_func_bad.py | 16 +++++++++ test_regress/t/t_randsequence_func_bad.v | 40 ++++++++++++++++++++++ 9 files changed, 164 insertions(+), 28 deletions(-) delete mode 100644 test_regress/t/t_randsequence_func.out create mode 100644 test_regress/t/t_randsequence_func_bad.out create mode 100755 test_regress/t/t_randsequence_func_bad.py create mode 100644 test_regress/t/t_randsequence_func_bad.v diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 66b9abcc6..2f854d7e1 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -2164,9 +2164,12 @@ class LinkDotFindVisitor final : public VNVisitor { if (nodep->fvarp()) nodep->fvarp()->v3warn(E_UNSUPPORTED, "Unsupported: randsequence production function variable"); - if (nodep->portsp()) - nodep->portsp()->v3warn(E_UNSUPPORTED, - "Unsupported: randsequence production function ports"); + // Mark formal ports as port-checked so the primary resolve pass does not + // flag them with "does not appear in port list" -- V3RandSequence will + // later move them onto the generated task as real input ports. + for (AstNode* itp = nodep->portsp(); itp; itp = itp->nextp()) { + VN_AS(itp, Var)->user4(true); + } iterateChildren(nodep); } diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index f75526eac..c185e6f52 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -56,6 +56,7 @@ class LinkParseVisitor final : public VNVisitor { bool m_inInterface = false; // True when inside interface declaration AstNodeProcedure* m_procedurep = nullptr; // Current procedure AstNodeFTask* m_ftaskp = nullptr; // Current task + AstRSProd* m_rsProdp = nullptr; // Current randsequence production AstNodeBlock* m_blockp = nullptr; // Current AstNodeBlock AstNodeStmt* m_blockAddAutomaticStmtp = nullptr; // Initial statements to add to block AstNodeStmt* m_blockAddStaticStmtp = nullptr; // Initial statements to add to block @@ -498,8 +499,10 @@ class LinkParseVisitor final : public VNVisitor { // Earlier moved any valuep() under the duplicate to the IO declaration UINFO(9, "VarInit case0 " << nodep); } else if (nodep->isParam() || nodep->isGenVar() - || (m_ftaskp && (nodep->isNonOutput() || nodep->isFuncReturn()))) { - // 1. Parameters and function inputs: It's a default to use if not overridden + || (m_ftaskp && (nodep->isNonOutput() || nodep->isFuncReturn())) + || (m_rsProdp && nodep->isNonOutput())) { + // 1. Parameters, function inputs, and randsequence production formal + // ports (IEEE 1800-2023 18.17.7): default to use if not overridden UINFO(9, "VarInit case1 " << nodep); } else if (!m_ftaskp && !VN_IS(m_modp, Class) && nodep->isNonOutput() && !nodep->isInput()) { @@ -889,6 +892,11 @@ class LinkParseVisitor final : public VNVisitor { } iterateChildren(nodep); } + void visit(AstRSProd* nodep) override { + VL_RESTORER(m_rsProdp); + m_rsProdp = nodep; + iterateChildren(nodep); + } void visit(AstNodeBlock* nodep) override { VL_RESTORER(m_blockAddAutomaticStmtp); m_blockAddAutomaticStmtp = nullptr; diff --git a/src/V3RandSequence.cpp b/src/V3RandSequence.cpp index efdb45b69..f8f6b155c 100644 --- a/src/V3RandSequence.cpp +++ b/src/V3RandSequence.cpp @@ -141,26 +141,32 @@ class RandSequenceVisitor final : public VNVisitor { taskp->addStmtsp(breakVarp); // Call the start production's task - taskp->addStmtsp(newProdFuncRef(nodep, m_startProdp, breakVarp)); + taskp->addStmtsp(newProdFuncRef(nodep, m_startProdp, breakVarp, nullptr)); UINFOTREE(9, taskp, "newStart", ""); return taskp; } - AstNode* newProdFuncRef(AstNode* nodep, AstRSProd* prodp, AstVar* breakVarp) { + AstNode* newProdFuncRef(AstNode* nodep, AstRSProd* prodp, AstVar* breakVarp, + AstArg* userArgsp) { auto it = m_prodFuncps.find(prodp); UASSERT_OBJ(it != m_prodFuncps.end(), nodep, "No production function made"); AstNodeFTask* const prodFuncp = it->second; FileLine* const fl = nodep->fileline(); - AstArg* const argsp - = new AstArg{fl, breakVarp->name(), new AstVarRef{fl, breakVarp, VAccess::WRITE}}; + // V3Width already ran before V3RandSequence, so VarRefs we create here + // need dtype set explicitly, V3Broken later checks width == widthMin. + AstVarRef* const breakRefp = new AstVarRef{fl, breakVarp, VAccess::WRITE}; + breakRefp->dtypeFrom(breakVarp); + AstArg* const argsp = new AstArg{fl, breakVarp->name(), breakRefp}; for (const auto& itr : m_localizeNames) { const AstVar* const lvarp = itr.second; AstVar* const iovarp = m_localizeRemaps[lvarp]; UASSERT_OBJ(iovarp, nodep, "No new port variable for local variable" << lvarp); - argsp->addNext(new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(), - new AstVarRef{fl, iovarp, VAccess::READWRITE}}); + AstVarRef* const refp = new AstVarRef{fl, iovarp, VAccess::READWRITE}; + refp->dtypeFrom(iovarp); + argsp->addNext(new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(), refp}); } + if (userArgsp) argsp->addNext(userArgsp); AstNode* const newp = new AstStmtExpr{fl, new AstTaskRef{fl, VN_AS(prodFuncp, Task), argsp}}; return newp; @@ -480,6 +486,19 @@ class RandSequenceVisitor final : public VNVisitor { m_breakVarp = newBreakVar(nodep->fileline(), true); m_prodFuncp->addStmtsp(m_breakVarp); + // Production formal ports become real input ports on the generated task. + // Must be added after m_breakVarp / localize ports so positional argument + // order in newProdFuncRef stays consistent: [break, localize..., user...]. + if (AstNode* const portsp = nodep->portsp()) { + portsp->unlinkFrBackWithNext(); + m_prodFuncp->addStmtsp(portsp); + for (AstNode* itp = portsp; itp; itp = itp->nextp()) { + AstVar* const portVarp = VN_AS(itp, Var); + portVarp->funcLocal(true); + portVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + } + } + // Put JumpBlock immediately under the new function to support // a future break/return. V3Const will rip it out if unneeded. VL_RESTORER(m_jumpBlockp); @@ -489,9 +508,6 @@ class RandSequenceVisitor final : public VNVisitor { if (nodep->fvarp()) nodep->fvarp()->v3warn(E_UNSUPPORTED, "Unsupported: randsequence production function variable"); - if (nodep->portsp()) - nodep->portsp()->v3warn(E_UNSUPPORTED, - "Unsupported: randsequence production function ports"); // Move children into m_prodFuncp, and iterate there if (!nodep->rulesp()) { // Nothing to do @@ -579,8 +595,11 @@ class RandSequenceVisitor final : public VNVisitor { UASSERT_OBJ(m_prodFuncp, nodep, "RSProdItem not under production"); AstRSProd* const foundp = nodep->prodp(); UASSERT_OBJ(foundp, nodep, "Unlinked production reference"); + // Pass through caller-side argument list (IEEE 1800-2023 18.17.7). + AstArg* const userArgsp + = nodep->argsp() ? nodep->argsp()->unlinkFrBackWithNext() : nullptr; // Convert to task call - AstNode* const newp = newProdFuncRef(nodep, foundp, m_breakVarp); + AstNode* const newp = newProdFuncRef(nodep, foundp, m_breakVarp, userArgsp); // The production might have done a "break;", skip other steps if so newp->addNext(new AstIf{nodep->fileline(), new AstVarRef{nodep->fileline(), m_breakVarp, VAccess::READ}, diff --git a/test_regress/t/t_randsequence_func.out b/test_regress/t/t_randsequence_func.out deleted file mode 100644 index 85994f492..000000000 --- a/test_regress/t/t_randsequence_func.out +++ /dev/null @@ -1,9 +0,0 @@ -%Error-UNSUPPORTED: t/t_randsequence_func.v:33:23: Unsupported: randsequence production function ports - 33 | void func(int n) : { counts[1] += n; }; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: t/t_randsequence_func.v:33:23: Input/output/inout does not appear in port list: 'n' - 33 | void func(int n) : { counts[1] += n; }; - | ^ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: Exiting due to diff --git a/test_regress/t/t_randsequence_func.py b/test_regress/t/t_randsequence_func.py index 20b96a7d7..3cc73805c 100755 --- a/test_regress/t/t_randsequence_func.py +++ b/test_regress/t/t_randsequence_func.py @@ -11,9 +11,8 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(fails=test.vlt_all, expect_filename=test.golden_filename) +test.compile() -if not test.vlt_all: - test.execute() +test.execute() test.passes() diff --git a/test_regress/t/t_randsequence_func.v b/test_regress/t/t_randsequence_func.v index d02b5d53a..ac06220bc 100644 --- a/test_regress/t/t_randsequence_func.v +++ b/test_regress/t/t_randsequence_func.v @@ -16,13 +16,14 @@ module t; int seq; int counts[8]; + int sum, hits; task prep(); for (int i = 0; i < COUNT; ++i) counts[i] = 0; endtask initial begin - // functions + // Single-port and no-port productions prep(); for (int i = 0; i < COUNT; ++i) begin randsequence(main) @@ -37,6 +38,42 @@ module t; `checkd(counts[1], COUNT * (10 + 20)); `checkd(counts[2], COUNT * 1 / 1); // return + // Multi-port, repeat-with-arg, if-prod-with-arg, nested-prod-with-arg + sum = 0; + hits = 0; + for (int i = 0; i < COUNT; ++i) begin + // verilog_format: off + randsequence(main) + main : multi nested ifcall reps; + multi : add2(3, 4); + nested : leaf(7); + ifcall : if (1) add2(1, 2) else add2(0, 0); + reps : repeat (3) add2(2, 0); + void add2(int a, int b) : { sum = sum + a + b; hits = hits + 1; }; + void leaf(int v) : { sum = sum + v; hits = hits + 1; }; + endsequence + // verilog_format: on + end + `checkd(sum, COUNT * (7 + 7 + 3 + 3 * 2)); + `checkd(hits, COUNT * (1 + 1 + 1 + 3)); + + // Default port values (IEEE 1800-2023 18.17.7) + sum = 0; + hits = 0; + for (int i = 0; i < COUNT; ++i) begin + // verilog_format: off + randsequence(main) + main : useDefault override1 override2; + useDefault : add_def; + override1 : add_def(50); + override2 : add_def(100); + void add_def(int n = 7) : { sum = sum + n; hits = hits + 1; }; + endsequence + // verilog_format: on + end + `checkd(sum, COUNT * (7 + 50 + 100)); + `checkd(hits, COUNT * 3); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_randsequence_func_bad.out b/test_regress/t/t_randsequence_func_bad.out new file mode 100644 index 000000000..a6d4e0cff --- /dev/null +++ b/test_regress/t/t_randsequence_func_bad.out @@ -0,0 +1,23 @@ +%Error: t/t_randsequence_func_bad.v:13:21: Too many arguments in call to task 't.__Vrs0_add' + 13 | main : add(1, 2); + | ^~~ + : ... Location of task 't.__Vrs0_add' declaration: + 14 | void add(int y) : { $display(y); }; + | ^~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_randsequence_func_bad.v:19:14: Missing argument on non-defaulted argument 'y' in function call to TASK 't.__Vrs1_add' + 19 | main : add(); + | ^~~ +%Error: t/t_randsequence_func_bad.v:25:19: No such argument 'bogus' in call to task 't.__Vrs2_add' + 25 | main : add(.bogus(1)); + | ^~~ + : ... Location of task 't.__Vrs2_add' declaration + 26 | void add(int y) : { $display(y); }; + | ^~~ +%Error: t/t_randsequence_func_bad.v:25:14: Missing argument on non-defaulted argument 'y' in function call to TASK 't.__Vrs2_add' + 25 | main : add(.bogus(1)); + | ^~~ +%Error: t/t_randsequence_func_bad.v:31:26: Duplicate argument 'y' in function call to TASK 't.__Vrs3_add' + 31 | main : add(.y(1), .y(2)); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_randsequence_func_bad.py b/test_regress/t/t_randsequence_func_bad.py new file mode 100755 index 000000000..05284f5ad --- /dev/null +++ b/test_regress/t/t_randsequence_func_bad.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('linter') + +test.lint(verilator_flags=["--lint-only"], fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_randsequence_func_bad.v b/test_regress/t/t_randsequence_func_bad.v new file mode 100644 index 000000000..90057b8fa --- /dev/null +++ b/test_regress/t/t_randsequence_func_bad.v @@ -0,0 +1,40 @@ +// 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 + +module t; + + // verilog_format: off + initial begin + // Too many actuals. + randsequence(main) + main : add(1, 2); + void add(int y) : { $display(y); }; + endsequence + + // Too few actuals (non-defaulted formal). + randsequence(main) + main : add(); + void add(int y) : { $display(y); }; + endsequence + + // Named argument with non-existent name. + randsequence(main) + main : add(.bogus(1)); + void add(int y) : { $display(y); }; + endsequence + + // Duplicate named argument. + randsequence(main) + main : add(.y(1), .y(2)); + void add(int y) : { $display(y); }; + endsequence + + $write("*-* All Finished *-*\n"); + $finish; + end + // verilog_format: on + +endmodule