diff --git a/src/V3Undriven.cpp b/src/V3Undriven.cpp index cc890399b..d9a7a31e7 100644 --- a/src/V3Undriven.cpp +++ b/src/V3Undriven.cpp @@ -51,6 +51,7 @@ class UndrivenVarEntry final { const AstNode* m_procWritep = nullptr; // varref if written in process const FileLine* m_nodeFileLinep = nullptr; // File line of varref if driven, else nullptr bool m_underGen = false; // Under a generate + bool m_ftaskDriven = false; // Last driven by function or task const AstNodeFTaskRef* m_callNodep = nullptr; // Call node if driven via writeSummary @@ -124,7 +125,8 @@ public: UINFO(9, "set d[*] " << m_varp->name()); m_wholeFlags[FLAG_DRIVEN] = true; } - void drivenWhole(const AstNodeVarRef* nodep, const FileLine* fileLinep) { + void drivenWhole(const AstNodeVarRef* nodep, const FileLine* fileLinep, bool ftaskDef) { + m_ftaskDriven = ftaskDef && !isDrivenWhole(); drivenWhole(); m_nodep = nodep; m_nodeFileLinep = fileLinep; @@ -143,6 +145,7 @@ public: bool isUnderGen() const { return m_underGen; } bool isDrivenWhole() const { return m_wholeFlags[FLAG_DRIVEN]; } bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; } + bool isFtaskDriven() const { return m_ftaskDriven; } const AstNodeVarRef* getNodep() const { return m_nodep; } const FileLine* getNodeFileLinep() const { return m_nodeFileLinep; } const AstAlways* getAlwCombp() const { return m_alwCombp; } @@ -207,6 +210,8 @@ public: true); // Warn only once } } else { // Signal + const string varType{nodep->isFuncLocal() ? "Function variable" : "Signal"}; + bool funcInout = nodep->isFuncLocal() && nodep->isInout(); bool allU = true; bool allD = true; bool anyU = m_wholeFlags[FLAG_USED]; @@ -225,6 +230,10 @@ public: anyDnotU |= !used && driv; anynotDU |= !used && !driv; } + if (funcInout) { + if (anyD) allU = true; + allD = true; + } if (allU) m_wholeFlags[FLAG_USED] = true; if (allD) m_wholeFlags[FLAG_DRIVEN] = true; // Test results @@ -239,37 +248,45 @@ public: // thus undriven+unused bits get UNUSED warnings, as they're not as buggy. if (!unusedMatch(nodep)) { nodep->v3warn(UNUSEDSIGNAL, - "Signal is not driven, nor used: " << nodep->prettyNameQ()); + varType << " is not driven, nor used: " << nodep->prettyNameQ()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once } } else if (allD && !anyU) { if (!unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " << nodep->prettyNameQ()); + nodep->v3warn(UNUSEDSIGNAL, + varType << " is not used: " << nodep->prettyNameQ()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once } } else if (!anyD && allU) { - nodep->v3warn(UNDRIVEN, "Signal is not driven: " << nodep->prettyNameQ()); + nodep->v3warn(UNDRIVEN, varType << " is not driven: " << nodep->prettyNameQ()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once - } else { + } else if (!funcInout) { // Bits have different dispositions + const std::string varTypeLower = [&varType]() { + std::string str = varType; + str[0] = std::tolower(static_cast(str[0])); + return str; + }(); bool setU = false; bool setD = false; if (anynotDU && !unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: " - << nodep->prettyNameQ() << bitNames(BN_BOTH)); + nodep->v3warn(UNUSEDSIGNAL, + "Bits of " << varTypeLower << " are not driven, nor used: " + << nodep->prettyNameQ() << bitNames(BN_BOTH)); setU = true; } if (anyDnotU && !unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, - "Bits of signal are not used: " << nodep->prettyNameQ() - << bitNames(BN_UNUSED)); + nodep->v3warn(UNUSEDSIGNAL, "Bits of " << varTypeLower << " are not used: " + << nodep->prettyNameQ() + << bitNames(BN_UNUSED)); setU = true; } if (anyUnotD) { - nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: " - << nodep->prettyNameQ() << bitNames(BN_UNDRIVEN)); + nodep->v3warn(UNDRIVEN, "Bits of " << varTypeLower << " are not driven: " + << nodep->prettyNameQ() + << bitNames(BN_UNDRIVEN)); setD = true; } if (setU) { // Warn only once @@ -354,18 +371,20 @@ class UndrivenVisitor final : public VNVisitorConst { // VISITORS void visit(AstVar* nodep) override { + const bool funcInout = nodep->isFuncLocal() && nodep->isInout(); for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) { // For assigns and non-combo always, do just usr==1, to look // for module-wide undriven etc. // For combo always, run both usr==1 for above, and also // usr==2 for always-only checks. UndrivenVarEntry* const entryp = getEntryp(nodep, usr); - if (nodep->isNonOutput() || nodep->isSigPublic() || nodep->isSigUserRWPublic() + if ((nodep->isNonOutput() && !funcInout) || nodep->isSigPublic() + || nodep->isSigUserRWPublic() || (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) { entryp->drivenWhole(); } - if (nodep->isWritable() || nodep->isSigPublic() || nodep->isSigUserRWPublic() - || nodep->isSigUserRdPublic() + if ((nodep->isWritable() && !funcInout) || nodep->isSigPublic() + || nodep->isSigUserRWPublic() || nodep->isSigUserRdPublic() || (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) { entryp->usedWhole(); } @@ -433,12 +452,12 @@ class UndrivenVisitor final : public VNVisitorConst { } // If writeSummary is enabled, task/function definitions are treated as non-executed. - // Their effects are applied at call sites via writeSummary(), so don't let definition - // traversal create phantom "other writes" for MULTIDRIVEN. + // Remember that anything driven here doesn't count toward MULTIDRIVEN. + bool ftaskDef = false; if (m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox && !m_taskp->dpiExport()) { AstVar* const retVarp = VN_CAST(m_taskp->fvarp(), Var); - if (!retVarp || nodep->varp() != retVarp) return; + if (!retVarp || nodep->varp() != retVarp) ftaskDef = true; } for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) { @@ -453,7 +472,8 @@ class UndrivenVisitor final : public VNVisitorConst { if (entryp->isDrivenWhole() && !m_inBBox && !VN_IS(nodep, VarXRef) && !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType) && nodep->fileline() != entryp->getNodeFileLinep() && !entryp->isUnderGen() - && (entryp->getNodep() || entryp->callNodep())) { + && (entryp->getNodep() || entryp->callNodep()) && !entryp->isFtaskDriven() + && !ftaskDef) { const AstNode* const otherWritep = entryp->getNodep() ? static_cast(entryp->getNodep()) @@ -483,7 +503,7 @@ class UndrivenVisitor final : public VNVisitorConst { << otherWritep->warnContextSecondary()); } } - entryp->drivenWhole(nodep, nodep->fileline()); + entryp->drivenWhole(nodep, nodep->fileline(), ftaskDef); if (m_alwaysCombp && entryp->isDrivenAlwaysCombWhole() && m_alwaysCombp != entryp->getAlwCombp() && m_alwaysCombp->fileline() == entryp->getAlwCombFileLinep()) diff --git a/test_regress/t/t_lint_style_bad.out b/test_regress/t/t_lint_style_bad.out index 7de1ed0bc..0bdd6124a 100644 --- a/test_regress/t/t_lint_style_bad.out +++ b/test_regress/t/t_lint_style_bad.out @@ -23,7 +23,7 @@ | ^~~ ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. -%Warning-UNDRIVEN: t/t_lint_style_bad.v:12:14: Signal is not driven: 'top' +%Warning-UNDRIVEN: t/t_lint_style_bad.v:12:14: Function variable is not driven: 'top' : ... note: In instance 't' 12 | output top; | ^~~ diff --git a/test_regress/t/t_lint_unused_func_bad.out b/test_regress/t/t_lint_unused_func_bad.out new file mode 100644 index 000000000..d30486aab --- /dev/null +++ b/test_regress/t/t_lint_unused_func_bad.out @@ -0,0 +1,29 @@ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_func_bad.v:14:53: Function variable is not used: 'not_used' + : ... note: In instance 't' + 14 | function automatic int unused_input_unused_func(int not_used); + | ^~~~~~~~ + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. +%Warning-UNDRIVEN: t/t_lint_unused_func_bad.v:20:7: Function variable is not driven: 'not_driven' + : ... note: In instance 't' + 20 | int not_driven; + | ^~~~~~~~~~ + ... For warning description see https://verilator.org/warn/UNDRIVEN?v=latest + ... Use "/* verilator lint_off UNDRIVEN */" and lint_on around source to disable this message. +%Warning-UNDRIVEN: t/t_lint_unused_func_bad.v:25:7: Function variable is not driven: 'undriven_result' + : ... note: In instance 't' + 25 | int undriven_result; + | ^~~~~~~~~~~~~~~ +%Warning-UNDRIVEN: t/t_lint_unused_func_bad.v:29:52: Function variable is not driven: 'undriven_out_param' + : ... note: In instance 't' + 29 | function automatic void undriven_output(output int undriven_out_param); + | ^~~~~~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_func_bad.v:32:51: Function variable is not driven, nor used: 'untouched_inout_param' + : ... note: In instance 't' + 32 | function automatic void untouched_inout(inout int untouched_inout_param); + | ^~~~~~~~~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_func_bad.v:35:63: Function variable is not driven, nor used: 'untouched_inout_unused_func_param' + : ... note: In instance 't' + 35 | function automatic void untouched_inout_unused_func(inout int untouched_inout_unused_func_param); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_unused_func_bad.py b/test_regress/t/t_lint_unused_func_bad.py new file mode 100755 index 000000000..200d5ae43 --- /dev/null +++ b/test_regress/t/t_lint_unused_func_bad.py @@ -0,0 +1,20 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios("simulator") + +test.compile( + verilator_flags2=["--lint-only -Wall -Wno-DECLFILENAME --unused-regexp blargh"], + fails=True, + expect_filename=test.golden_filename, +) + +test.passes() diff --git a/test_regress/t/t_lint_unused_func_bad.v b/test_regress/t/t_lint_unused_func_bad.v new file mode 100644 index 000000000..1dc0053dc --- /dev/null +++ b/test_regress/t/t_lint_unused_func_bad.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +function automatic int ok_unused_func(real val); + int result = $rtoi(val); + bit huh = result[0]; + return result + huh; +endfunction + +// Unused parameter +function automatic int unused_input_unused_func(int not_used); + return 5; +endfunction + +// Undriven variable +function automatic int undriven_var_unused_func(int some_val); + int not_driven; + return some_val + not_driven; +endfunction + +function automatic int undriven_var(); + int undriven_result; + return undriven_result; +endfunction + +function automatic void undriven_output(output int undriven_out_param); +endfunction + +function automatic void untouched_inout(inout int untouched_inout_param); +endfunction + +function automatic void untouched_inout_unused_func(inout int untouched_inout_unused_func_param); +endfunction + +function automatic void driven_inout_unused_func(inout int driven_inout_unused_func_param); + driven_inout_unused_func_param = 7; +endfunction + +function automatic void used_inout_unused_func(inout int used_inout_unused_func_param); + $display(used_inout_unused_func_param); +endfunction + +module t; + int result; + initial begin + result = undriven_var(); + undriven_output(result); + untouched_inout(result); + $display(result); + end +endmodule