Fix UNUSED / UNDRIVEN for unused functions (#6967)
This commit is contained in:
parent
9b1b9a5b3b
commit
fed41aba91
|
|
@ -51,6 +51,7 @@ class UndrivenVarEntry final {
|
||||||
const AstNode* m_procWritep = nullptr; // varref if written in process
|
const AstNode* m_procWritep = nullptr; // varref if written in process
|
||||||
const FileLine* m_nodeFileLinep = nullptr; // File line of varref if driven, else nullptr
|
const FileLine* m_nodeFileLinep = nullptr; // File line of varref if driven, else nullptr
|
||||||
bool m_underGen = false; // Under a generate
|
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
|
const AstNodeFTaskRef* m_callNodep = nullptr; // Call node if driven via writeSummary
|
||||||
|
|
||||||
|
|
@ -124,7 +125,8 @@ public:
|
||||||
UINFO(9, "set d[*] " << m_varp->name());
|
UINFO(9, "set d[*] " << m_varp->name());
|
||||||
m_wholeFlags[FLAG_DRIVEN] = true;
|
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();
|
drivenWhole();
|
||||||
m_nodep = nodep;
|
m_nodep = nodep;
|
||||||
m_nodeFileLinep = fileLinep;
|
m_nodeFileLinep = fileLinep;
|
||||||
|
|
@ -143,6 +145,7 @@ public:
|
||||||
bool isUnderGen() const { return m_underGen; }
|
bool isUnderGen() const { return m_underGen; }
|
||||||
bool isDrivenWhole() const { return m_wholeFlags[FLAG_DRIVEN]; }
|
bool isDrivenWhole() const { return m_wholeFlags[FLAG_DRIVEN]; }
|
||||||
bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; }
|
bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; }
|
||||||
|
bool isFtaskDriven() const { return m_ftaskDriven; }
|
||||||
const AstNodeVarRef* getNodep() const { return m_nodep; }
|
const AstNodeVarRef* getNodep() const { return m_nodep; }
|
||||||
const FileLine* getNodeFileLinep() const { return m_nodeFileLinep; }
|
const FileLine* getNodeFileLinep() const { return m_nodeFileLinep; }
|
||||||
const AstAlways* getAlwCombp() const { return m_alwCombp; }
|
const AstAlways* getAlwCombp() const { return m_alwCombp; }
|
||||||
|
|
@ -207,6 +210,8 @@ public:
|
||||||
true); // Warn only once
|
true); // Warn only once
|
||||||
}
|
}
|
||||||
} else { // Signal
|
} else { // Signal
|
||||||
|
const string varType{nodep->isFuncLocal() ? "Function variable" : "Signal"};
|
||||||
|
bool funcInout = nodep->isFuncLocal() && nodep->isInout();
|
||||||
bool allU = true;
|
bool allU = true;
|
||||||
bool allD = true;
|
bool allD = true;
|
||||||
bool anyU = m_wholeFlags[FLAG_USED];
|
bool anyU = m_wholeFlags[FLAG_USED];
|
||||||
|
|
@ -225,6 +230,10 @@ public:
|
||||||
anyDnotU |= !used && driv;
|
anyDnotU |= !used && driv;
|
||||||
anynotDU |= !used && !driv;
|
anynotDU |= !used && !driv;
|
||||||
}
|
}
|
||||||
|
if (funcInout) {
|
||||||
|
if (anyD) allU = true;
|
||||||
|
allD = true;
|
||||||
|
}
|
||||||
if (allU) m_wholeFlags[FLAG_USED] = true;
|
if (allU) m_wholeFlags[FLAG_USED] = true;
|
||||||
if (allD) m_wholeFlags[FLAG_DRIVEN] = true;
|
if (allD) m_wholeFlags[FLAG_DRIVEN] = true;
|
||||||
// Test results
|
// Test results
|
||||||
|
|
@ -239,37 +248,45 @@ public:
|
||||||
// thus undriven+unused bits get UNUSED warnings, as they're not as buggy.
|
// thus undriven+unused bits get UNUSED warnings, as they're not as buggy.
|
||||||
if (!unusedMatch(nodep)) {
|
if (!unusedMatch(nodep)) {
|
||||||
nodep->v3warn(UNUSEDSIGNAL,
|
nodep->v3warn(UNUSEDSIGNAL,
|
||||||
"Signal is not driven, nor used: " << nodep->prettyNameQ());
|
varType << " is not driven, nor used: " << nodep->prettyNameQ());
|
||||||
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
|
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
|
||||||
true); // Warn only once
|
true); // Warn only once
|
||||||
}
|
}
|
||||||
} else if (allD && !anyU) {
|
} else if (allD && !anyU) {
|
||||||
if (!unusedMatch(nodep)) {
|
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,
|
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
|
||||||
true); // Warn only once
|
true); // Warn only once
|
||||||
}
|
}
|
||||||
} else if (!anyD && allU) {
|
} 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
|
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once
|
||||||
} else {
|
} else if (!funcInout) {
|
||||||
// Bits have different dispositions
|
// Bits have different dispositions
|
||||||
|
const std::string varTypeLower = [&varType]() {
|
||||||
|
std::string str = varType;
|
||||||
|
str[0] = std::tolower(static_cast<unsigned char>(str[0]));
|
||||||
|
return str;
|
||||||
|
}();
|
||||||
bool setU = false;
|
bool setU = false;
|
||||||
bool setD = false;
|
bool setD = false;
|
||||||
if (anynotDU && !unusedMatch(nodep)) {
|
if (anynotDU && !unusedMatch(nodep)) {
|
||||||
nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: "
|
nodep->v3warn(UNUSEDSIGNAL,
|
||||||
<< nodep->prettyNameQ() << bitNames(BN_BOTH));
|
"Bits of " << varTypeLower << " are not driven, nor used: "
|
||||||
|
<< nodep->prettyNameQ() << bitNames(BN_BOTH));
|
||||||
setU = true;
|
setU = true;
|
||||||
}
|
}
|
||||||
if (anyDnotU && !unusedMatch(nodep)) {
|
if (anyDnotU && !unusedMatch(nodep)) {
|
||||||
nodep->v3warn(UNUSEDSIGNAL,
|
nodep->v3warn(UNUSEDSIGNAL, "Bits of " << varTypeLower << " are not used: "
|
||||||
"Bits of signal are not used: " << nodep->prettyNameQ()
|
<< nodep->prettyNameQ()
|
||||||
<< bitNames(BN_UNUSED));
|
<< bitNames(BN_UNUSED));
|
||||||
setU = true;
|
setU = true;
|
||||||
}
|
}
|
||||||
if (anyUnotD) {
|
if (anyUnotD) {
|
||||||
nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: "
|
nodep->v3warn(UNDRIVEN, "Bits of " << varTypeLower << " are not driven: "
|
||||||
<< nodep->prettyNameQ() << bitNames(BN_UNDRIVEN));
|
<< nodep->prettyNameQ()
|
||||||
|
<< bitNames(BN_UNDRIVEN));
|
||||||
setD = true;
|
setD = true;
|
||||||
}
|
}
|
||||||
if (setU) { // Warn only once
|
if (setU) { // Warn only once
|
||||||
|
|
@ -354,18 +371,20 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
void visit(AstVar* nodep) override {
|
void visit(AstVar* nodep) override {
|
||||||
|
const bool funcInout = nodep->isFuncLocal() && nodep->isInout();
|
||||||
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
|
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
|
||||||
// For assigns and non-combo always, do just usr==1, to look
|
// For assigns and non-combo always, do just usr==1, to look
|
||||||
// for module-wide undriven etc.
|
// for module-wide undriven etc.
|
||||||
// For combo always, run both usr==1 for above, and also
|
// For combo always, run both usr==1 for above, and also
|
||||||
// usr==2 for always-only checks.
|
// usr==2 for always-only checks.
|
||||||
UndrivenVarEntry* const entryp = getEntryp(nodep, usr);
|
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()))) {
|
|| (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) {
|
||||||
entryp->drivenWhole();
|
entryp->drivenWhole();
|
||||||
}
|
}
|
||||||
if (nodep->isWritable() || nodep->isSigPublic() || nodep->isSigUserRWPublic()
|
if ((nodep->isWritable() && !funcInout) || nodep->isSigPublic()
|
||||||
|| nodep->isSigUserRdPublic()
|
|| nodep->isSigUserRWPublic() || nodep->isSigUserRdPublic()
|
||||||
|| (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) {
|
|| (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) {
|
||||||
entryp->usedWhole();
|
entryp->usedWhole();
|
||||||
}
|
}
|
||||||
|
|
@ -433,12 +452,12 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If writeSummary is enabled, task/function definitions are treated as non-executed.
|
// 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
|
// Remember that anything driven here doesn't count toward MULTIDRIVEN.
|
||||||
// traversal create phantom "other writes" for MULTIDRIVEN.
|
bool ftaskDef = false;
|
||||||
if (m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox
|
if (m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox
|
||||||
&& !m_taskp->dpiExport()) {
|
&& !m_taskp->dpiExport()) {
|
||||||
AstVar* const retVarp = VN_CAST(m_taskp->fvarp(), Var);
|
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) {
|
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)
|
if (entryp->isDrivenWhole() && !m_inBBox && !VN_IS(nodep, VarXRef)
|
||||||
&& !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
|
&& !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
|
||||||
&& nodep->fileline() != entryp->getNodeFileLinep() && !entryp->isUnderGen()
|
&& nodep->fileline() != entryp->getNodeFileLinep() && !entryp->isUnderGen()
|
||||||
&& (entryp->getNodep() || entryp->callNodep())) {
|
&& (entryp->getNodep() || entryp->callNodep()) && !entryp->isFtaskDriven()
|
||||||
|
&& !ftaskDef) {
|
||||||
|
|
||||||
const AstNode* const otherWritep
|
const AstNode* const otherWritep
|
||||||
= entryp->getNodep() ? static_cast<const AstNode*>(entryp->getNodep())
|
= entryp->getNodep() ? static_cast<const AstNode*>(entryp->getNodep())
|
||||||
|
|
@ -483,7 +503,7 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
<< otherWritep->warnContextSecondary());
|
<< otherWritep->warnContextSecondary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entryp->drivenWhole(nodep, nodep->fileline());
|
entryp->drivenWhole(nodep, nodep->fileline(), ftaskDef);
|
||||||
if (m_alwaysCombp && entryp->isDrivenAlwaysCombWhole()
|
if (m_alwaysCombp && entryp->isDrivenAlwaysCombWhole()
|
||||||
&& m_alwaysCombp != entryp->getAlwCombp()
|
&& m_alwaysCombp != entryp->getAlwCombp()
|
||||||
&& m_alwaysCombp->fileline() == entryp->getAlwCombFileLinep())
|
&& m_alwaysCombp->fileline() == entryp->getAlwCombFileLinep())
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
| ^~~
|
| ^~~
|
||||||
... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest
|
... 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.
|
... 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'
|
: ... note: In instance 't'
|
||||||
12 | output top;
|
12 | output top;
|
||||||
| ^~~
|
| ^~~
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue