Add MULTIDRIVEN checks for clocking block outputs (#7987)
This commit is contained in:
parent
daf5826610
commit
29fd2cf90d
|
|
@ -1469,6 +1469,13 @@ List Of Warnings
|
||||||
q <= d;
|
q <= d;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
A further case is when a signal named as a clocking block ``output`` is
|
||||||
|
also driven by a continuous assignment, or is named as an ``output`` of a
|
||||||
|
second clocking block. The clocking block drives the signal, so the design
|
||||||
|
and the testbench contend for it and the synchronous drive may be silently
|
||||||
|
lost. Declare the clocking block ``input`` if the intent is only to
|
||||||
|
observe the signal.
|
||||||
|
|
||||||
Ignoring this warning may hide clock domain crossing, timing, or
|
Ignoring this warning may hide clock domain crossing, timing, or
|
||||||
portability bugs. It may also cause longer simulation runtimes due to
|
portability bugs. It may also cause longer simulation runtimes due to
|
||||||
reduced optimizations.
|
reduced optimizations.
|
||||||
|
|
@ -1495,6 +1502,12 @@ List Of Warnings
|
||||||
|
|
||||||
.. include:: ../../docs/gen/ex_MULTIDRIVENPROC_msg.rst
|
.. include:: ../../docs/gen/ex_MULTIDRIVENPROC_msg.rst
|
||||||
|
|
||||||
|
Also warns when a signal named as a clocking block ``output`` is driven
|
||||||
|
by a plain ``always`` block. Driving a signal from both a clocking block
|
||||||
|
and a plain ``always`` block is a deliberate idiom in some testbenches,
|
||||||
|
so it is reported as MULTIDRIVENPROC rather than under the on-by-default
|
||||||
|
:option:`MULTIDRIVEN`.
|
||||||
|
|
||||||
To fix, drive the signal from a single ``always`` block, or use
|
To fix, drive the signal from a single ``always`` block, or use
|
||||||
``always_ff``/``always_comb`` if the intent is a single specialized
|
``always_ff``/``always_comb`` if the intent is a single specialized
|
||||||
process.
|
process.
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ class UndrivenVarEntry final {
|
||||||
= nullptr; // always_comb of var if driven within always_comb, else nullptr
|
= nullptr; // always_comb of var if driven within always_comb, else nullptr
|
||||||
const AstAlways* m_alwFFp = nullptr; // always_ff of var if driven within always_ff
|
const AstAlways* m_alwFFp = nullptr; // always_ff of var if driven within always_ff
|
||||||
const AstAlways* m_alwPlainp = nullptr; // plain always of var if driven within plain always
|
const AstAlways* m_alwPlainp = nullptr; // plain always of var if driven within plain always
|
||||||
|
const AstClocking* m_clockingp = nullptr; // clocking block of var if driven as output
|
||||||
const AstNodeVarRef* m_nodep = nullptr; // varref if driven, else nullptr
|
const AstNodeVarRef* m_nodep = nullptr; // varref if driven, else nullptr
|
||||||
const AstNode* m_initStaticp = nullptr; // varref if in InitialStatic driven
|
const AstNode* m_initStaticp = nullptr; // varref if in InitialStatic driven
|
||||||
const AstNode* m_initialp = nullptr; // varref if driven in an explicit initial block
|
const AstNode* m_initialp = nullptr; // varref if driven in an explicit initial block
|
||||||
|
|
@ -64,7 +65,8 @@ class UndrivenVarEntry final {
|
||||||
FLAG_DRIVEN_ALWCOMB = 2, // Whole signal has been driven from always_comb
|
FLAG_DRIVEN_ALWCOMB = 2, // Whole signal has been driven from always_comb
|
||||||
FLAG_DRIVEN_ALWFF = 3, // Whole signal has been driven from always_ff
|
FLAG_DRIVEN_ALWFF = 3, // Whole signal has been driven from always_ff
|
||||||
FLAG_DRIVEN_ALWPLAIN = 4, // Whole signal has been driven from a plain always
|
FLAG_DRIVEN_ALWPLAIN = 4, // Whole signal has been driven from a plain always
|
||||||
FLAGS_PER_BIT = 5 // Number of flags stored for each tracked bit
|
FLAG_DRIVEN_CLOCKING = 5, // Whole signal has been driven as a clocking block output
|
||||||
|
FLAGS_PER_BIT = 6 // Number of flags stored for each tracked bit
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -164,6 +166,10 @@ public:
|
||||||
m_wholeFlags[FLAG_DRIVEN_ALWPLAIN] = true;
|
m_wholeFlags[FLAG_DRIVEN_ALWPLAIN] = true;
|
||||||
m_alwPlainp = alwPlainp;
|
m_alwPlainp = alwPlainp;
|
||||||
}
|
}
|
||||||
|
void drivenClockingWhole(const AstClocking* clockingp) {
|
||||||
|
m_wholeFlags[FLAG_DRIVEN_CLOCKING] = true;
|
||||||
|
m_clockingp = clockingp;
|
||||||
|
}
|
||||||
|
|
||||||
const AstNode* initStaticp() const { return m_initStaticp; }
|
const AstNode* initStaticp() const { return m_initStaticp; }
|
||||||
void initStaticp(const AstNode* nodep) { m_initStaticp = nodep; }
|
void initStaticp(const AstNode* nodep) { m_initStaticp = nodep; }
|
||||||
|
|
@ -179,11 +185,13 @@ public:
|
||||||
bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; }
|
bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; }
|
||||||
bool isDrivenAlwaysFFWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWFF]; }
|
bool isDrivenAlwaysFFWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWFF]; }
|
||||||
bool isDrivenAlwaysPlainWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWPLAIN]; }
|
bool isDrivenAlwaysPlainWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWPLAIN]; }
|
||||||
|
bool isDrivenClockingWhole() const { return m_wholeFlags[FLAG_DRIVEN_CLOCKING]; }
|
||||||
bool isFtaskDriven() const { return m_ftaskDriven; }
|
bool isFtaskDriven() const { return m_ftaskDriven; }
|
||||||
const AstNodeVarRef* getNodep() const { return m_nodep; }
|
const AstNodeVarRef* getNodep() const { return m_nodep; }
|
||||||
const AstAlways* getAlwCombp() const { return m_alwCombp; }
|
const AstAlways* getAlwCombp() const { return m_alwCombp; }
|
||||||
const AstAlways* getAlwFFp() const { return m_alwFFp; }
|
const AstAlways* getAlwFFp() const { return m_alwFFp; }
|
||||||
const AstAlways* getAlwPlainp() const { return m_alwPlainp; }
|
const AstAlways* getAlwPlainp() const { return m_alwPlainp; }
|
||||||
|
const AstClocking* getClockingp() const { return m_clockingp; }
|
||||||
void usedBit(int bit, int width, const AstNode* nodep) {
|
void usedBit(int bit, int width, const AstNode* nodep) {
|
||||||
UINFO(9, "set u[" << (bit + width - 1) << ":" << bit << "] " << m_varp->name());
|
UINFO(9, "set u[" << (bit + width - 1) << ":" << bit << "] " << m_varp->name());
|
||||||
for (int i = 0; i < width; i++) {
|
for (int i = 0; i < width; i++) {
|
||||||
|
|
@ -384,6 +392,7 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
const AstAlways* m_alwaysCombp = nullptr; // Current always if combo, otherwise nullptr
|
const AstAlways* m_alwaysCombp = nullptr; // Current always if combo, otherwise nullptr
|
||||||
const AstAlways* m_alwaysFFp = nullptr; // Current always if ff, otherwise nullptr
|
const AstAlways* m_alwaysFFp = nullptr; // Current always if ff, otherwise nullptr
|
||||||
const AstAlways* m_alwaysPlainp = nullptr; // Current always if plain (not comb/ff/latch)
|
const AstAlways* m_alwaysPlainp = nullptr; // Current always if plain (not comb/ff/latch)
|
||||||
|
const AstClocking* m_clockingp = nullptr; // Current clocking block, otherwise nullptr
|
||||||
|
|
||||||
V3UndrivenCapture* const m_capturep = nullptr; // Capture object. 'nullptr' if disabled.
|
V3UndrivenCapture* const m_capturep = nullptr; // Capture object. 'nullptr' if disabled.
|
||||||
|
|
||||||
|
|
@ -431,6 +440,84 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
true); // Complain just once for any usage
|
true); // Complain just once for any usage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A clocking block 'output' is an additional driver of the signal it names.
|
||||||
|
// The conflict is found from whichever driver is reached second.
|
||||||
|
bool clockingDrivesOther(const UndrivenVarEntry* entryp, bool otherWriteIsStaticInit) const {
|
||||||
|
const bool otherIsExplicitProc
|
||||||
|
= entryp->isDrivenAlwaysCombWhole() || entryp->isDrivenAlwaysFFWhole();
|
||||||
|
return m_clockingp && !otherIsExplicitProc && !otherWriteIsStaticInit
|
||||||
|
&& m_clockingp != entryp->getClockingp();
|
||||||
|
}
|
||||||
|
bool otherDrivesClocking(const UndrivenVarEntry* entryp) const {
|
||||||
|
return !m_clockingp && !m_alwaysCombp && !m_alwaysFFp && !m_inInitialStatic
|
||||||
|
&& entryp->isDrivenClockingWhole();
|
||||||
|
}
|
||||||
|
// A continuous assignment or a second clocking block contending with a
|
||||||
|
// clocking block 'output' is an unambiguous driver conflict.
|
||||||
|
void warnClockingDriven(AstNodeVarRef* nodep, const UndrivenVarEntry* entryp,
|
||||||
|
const AstNode* otherWritep, bool otherWriteIsStaticInit) {
|
||||||
|
const AstClocking* const otherClockingp = entryp->getClockingp();
|
||||||
|
if (clockingDrivesOther(entryp, otherWriteIsStaticInit)) {
|
||||||
|
if (otherClockingp) {
|
||||||
|
nodep->v3warn(MULTIDRIVEN,
|
||||||
|
"Variable written to in clocking block also written by another "
|
||||||
|
"clocking block"
|
||||||
|
<< " (IEEE 1800-2023 14.3): " << nodep->prettyNameQ() << '\n'
|
||||||
|
<< nodep->warnOther() << '\n'
|
||||||
|
<< nodep->warnContextPrimary() << '\n'
|
||||||
|
<< otherWritep->warnOther()
|
||||||
|
<< "... Location of other clocking block output\n"
|
||||||
|
<< otherWritep->warnContextSecondary());
|
||||||
|
} else if (otherWritep == entryp->contAssignp()) {
|
||||||
|
nodep->v3warn(MULTIDRIVEN,
|
||||||
|
"Variable written to in clocking block also driven by continuous "
|
||||||
|
"assignment"
|
||||||
|
<< " (IEEE 1800-2023 14.3): " << nodep->prettyNameQ() << '\n'
|
||||||
|
<< nodep->warnOther() << '\n'
|
||||||
|
<< nodep->warnContextPrimary() << '\n'
|
||||||
|
<< otherWritep->warnOther()
|
||||||
|
<< "... Location of continuous assignment\n"
|
||||||
|
<< otherWritep->warnContextSecondary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (otherDrivesClocking(entryp) && m_inContAssign) {
|
||||||
|
nodep->v3warn(MULTIDRIVEN,
|
||||||
|
"Variable driven by continuous assignment also written to in "
|
||||||
|
"clocking block"
|
||||||
|
<< " (IEEE 1800-2023 14.3): " << nodep->prettyNameQ() << '\n'
|
||||||
|
<< nodep->warnOther() << '\n'
|
||||||
|
<< nodep->warnContextPrimary() << '\n'
|
||||||
|
<< otherWritep->warnOther()
|
||||||
|
<< "... Location of clocking block output\n"
|
||||||
|
<< otherWritep->warnContextSecondary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Driving a signal from both a clocking block and a plain process is a deliberate
|
||||||
|
// idiom in some testbenches, as with the plain always conflicts above.
|
||||||
|
void warnClockingDrivenProc(AstNodeVarRef* nodep, const UndrivenVarEntry* entryp,
|
||||||
|
const AstNode* otherWritep, bool otherWriteIsStaticInit) {
|
||||||
|
if (clockingDrivesOther(entryp, otherWriteIsStaticInit) && !entryp->getClockingp()
|
||||||
|
&& otherWritep != entryp->contAssignp()) {
|
||||||
|
nodep->v3warn(MULTIDRIVENPROC,
|
||||||
|
"Variable written to in clocking block also written by another "
|
||||||
|
"process"
|
||||||
|
<< " (IEEE 1800-2023 14.3): " << nodep->prettyNameQ() << '\n'
|
||||||
|
<< nodep->warnOther() << '\n'
|
||||||
|
<< nodep->warnContextPrimary() << '\n'
|
||||||
|
<< otherWritep->warnOther() << "... Location of other write\n"
|
||||||
|
<< otherWritep->warnContextSecondary());
|
||||||
|
}
|
||||||
|
if (otherDrivesClocking(entryp) && !m_inContAssign) {
|
||||||
|
nodep->v3warn(MULTIDRIVENPROC,
|
||||||
|
"Variable written to in process also written to in clocking block"
|
||||||
|
<< " (IEEE 1800-2023 14.3): " << nodep->prettyNameQ() << '\n'
|
||||||
|
<< nodep->warnOther() << '\n'
|
||||||
|
<< nodep->warnContextPrimary() << '\n'
|
||||||
|
<< otherWritep->warnOther()
|
||||||
|
<< "... Location of clocking block output\n"
|
||||||
|
<< otherWritep->warnContextSecondary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
void visit(AstVar* nodep) override {
|
void visit(AstVar* nodep) override {
|
||||||
|
|
@ -551,10 +638,10 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
// declaration's fileline, as v3warn suppression will check
|
// declaration's fileline, as v3warn suppression will check
|
||||||
// the driving fileline and still warn even if the warning
|
// the driving fileline and still warn even if the warning
|
||||||
// was suppressed with lint_off at the declaration.
|
// was suppressed with lint_off at the declaration.
|
||||||
|
const bool otherWriteIsStaticInit
|
||||||
|
= nodep->varp()->hasUserInit() && otherWritep == entryp->initStaticp();
|
||||||
if (multidrivenCommon
|
if (multidrivenCommon
|
||||||
&& !nodep->varp()->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVEN)) {
|
&& !nodep->varp()->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVEN)) {
|
||||||
const bool otherWriteIsStaticInit
|
|
||||||
= nodep->varp()->hasUserInit() && otherWritep == entryp->initStaticp();
|
|
||||||
if (m_alwaysCombp
|
if (m_alwaysCombp
|
||||||
&& (!entryp->isDrivenAlwaysCombWhole()
|
&& (!entryp->isDrivenAlwaysCombWhole()
|
||||||
|| (m_alwaysCombp != entryp->getAlwCombp()
|
|| (m_alwaysCombp != entryp->getAlwCombp()
|
||||||
|
|
@ -602,6 +689,7 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
<< "... Location of always_ff write\n"
|
<< "... Location of always_ff write\n"
|
||||||
<< otherWritep->warnContextSecondary());
|
<< otherWritep->warnContextSecondary());
|
||||||
}
|
}
|
||||||
|
warnClockingDriven(nodep, entryp, otherWritep, otherWriteIsStaticInit);
|
||||||
}
|
}
|
||||||
if (multidrivenCommon
|
if (multidrivenCommon
|
||||||
&& !nodep->varp()->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVENPROC)) {
|
&& !nodep->varp()->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVENPROC)) {
|
||||||
|
|
@ -634,6 +722,7 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
<< otherWritep->warnOther() << "... Location of other write\n"
|
<< otherWritep->warnOther() << "... Location of other write\n"
|
||||||
<< otherWritep->warnContextSecondary());
|
<< otherWritep->warnContextSecondary());
|
||||||
}
|
}
|
||||||
|
warnClockingDrivenProc(nodep, entryp, otherWritep, otherWriteIsStaticInit);
|
||||||
}
|
}
|
||||||
if (!m_inInitialSetup || nodep->varp()->hasUserInit()) {
|
if (!m_inInitialSetup || nodep->varp()->hasUserInit()) {
|
||||||
// Else don't count default initialization as a driver to a net/variable
|
// Else don't count default initialization as a driver to a net/variable
|
||||||
|
|
@ -646,6 +735,7 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
if (m_alwaysCombp) entryp->drivenAlwaysCombWhole(m_alwaysCombp);
|
if (m_alwaysCombp) entryp->drivenAlwaysCombWhole(m_alwaysCombp);
|
||||||
if (m_alwaysFFp) entryp->drivenAlwaysFFWhole(m_alwaysFFp, nodep->varp());
|
if (m_alwaysFFp) entryp->drivenAlwaysFFWhole(m_alwaysFFp, nodep->varp());
|
||||||
if (m_alwaysPlainp) entryp->drivenAlwaysPlainWhole(m_alwaysPlainp);
|
if (m_alwaysPlainp) entryp->drivenAlwaysPlainWhole(m_alwaysPlainp);
|
||||||
|
if (m_clockingp) entryp->drivenClockingWhole(m_clockingp);
|
||||||
}
|
}
|
||||||
if (nodep->access().isWriteOrRW() && !VN_IS(nodep, VarXRef)) {
|
if (nodep->access().isWriteOrRW() && !VN_IS(nodep, VarXRef)) {
|
||||||
// Ignoring xrefs as the initial and assignment to track might refer to two
|
// Ignoring xrefs as the initial and assignment to track might refer to two
|
||||||
|
|
@ -751,6 +841,11 @@ class UndrivenVisitor final : public VNVisitorConst {
|
||||||
iterateChildrenConst(nodep);
|
iterateChildrenConst(nodep);
|
||||||
if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9, " Done " << nodep);
|
if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9, " Done " << nodep);
|
||||||
}
|
}
|
||||||
|
void visit(AstClocking* nodep) override {
|
||||||
|
VL_RESTORER(m_clockingp);
|
||||||
|
m_clockingp = nodep;
|
||||||
|
iterateChildrenConst(nodep);
|
||||||
|
}
|
||||||
|
|
||||||
void visit(AstNodeFTaskRef* nodep) override {
|
void visit(AstNodeFTaskRef* nodep) override {
|
||||||
VL_RESTORER(m_inFTaskRef);
|
VL_RESTORER(m_inFTaskRef);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
%Warning-MULTIDRIVEN: t/t_lint_multidriven_clocking_bad.v:21:10: Variable driven by continuous assignment also written to in clocking block (IEEE 1800-2023 14.3): 'early_assign'
|
||||||
|
: ... note: In instance 't'
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:21:10:
|
||||||
|
21 | assign early_assign = 1'b1;
|
||||||
|
| ^~~~~~~~~~~~
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:17:12: ... Location of clocking block output
|
||||||
|
17 | output early_assign;
|
||||||
|
| ^~~~~~~~~~~~
|
||||||
|
... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest
|
||||||
|
... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message.
|
||||||
|
%Warning-MULTIDRIVENPROC: t/t_lint_multidriven_clocking_bad.v:22:25: Variable written to in process also written to in clocking block (IEEE 1800-2023 14.3): 'early_always'
|
||||||
|
: ... note: In instance 't'
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:22:25:
|
||||||
|
22 | always @(posedge clk) early_always <= 1'b1;
|
||||||
|
| ^~~~~~~~~~~~
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:18:12: ... Location of clocking block output
|
||||||
|
18 | output early_always;
|
||||||
|
| ^~~~~~~~~~~~
|
||||||
|
... For warning description see https://verilator.org/warn/MULTIDRIVENPROC?v=latest
|
||||||
|
... Use "/* verilator lint_off MULTIDRIVENPROC */" and lint_on around source to disable this message.
|
||||||
|
%Warning-MULTIDRIVEN: t/t_lint_multidriven_clocking_bad.v:45:12: Variable written to in clocking block also driven by continuous assignment (IEEE 1800-2023 14.3): 'late_assign'
|
||||||
|
: ... note: In instance 't'
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:45:12:
|
||||||
|
45 | output late_assign;
|
||||||
|
| ^~~~~~~~~~~
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:27:10: ... Location of continuous assignment
|
||||||
|
27 | assign late_assign = 1'b1;
|
||||||
|
| ^~~~~~~~~~~
|
||||||
|
%Warning-MULTIDRIVENPROC: t/t_lint_multidriven_clocking_bad.v:46:12: Variable written to in clocking block also written by another process (IEEE 1800-2023 14.3): 'late_always'
|
||||||
|
: ... note: In instance 't'
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:46:12:
|
||||||
|
46 | output late_always;
|
||||||
|
| ^~~~~~~~~~~
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:32:25: ... Location of other write
|
||||||
|
32 | always @(posedge clk) late_always <= 1'b1;
|
||||||
|
| ^~~~~~~~~~~
|
||||||
|
%Warning-MULTIDRIVEN: t/t_lint_multidriven_clocking_bad.v:53:12: Variable written to in clocking block also written by another clocking block (IEEE 1800-2023 14.3): 'dual_clocking'
|
||||||
|
: ... note: In instance 't'
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:53:12:
|
||||||
|
53 | output dual_clocking;
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
t/t_lint_multidriven_clocking_bad.v:47:12: ... Location of other clocking block output
|
||||||
|
47 | output dual_clocking;
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
%Error: Exiting due to
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/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')
|
||||||
|
|
||||||
|
# A clocking block 'output' drives the signal it names. Against a continuous
|
||||||
|
# assignment or a second clocking block that is an unambiguous driver conflict
|
||||||
|
# (MULTIDRIVEN, on by default); against a plain always block it is a common
|
||||||
|
# testbench idiom, so it is only reported under MULTIDRIVENPROC, enabled here
|
||||||
|
# explicitly. Signals driven by a single clocking block, only read by one, named
|
||||||
|
# by two clockvars of one clocking block, or carrying just a declaration
|
||||||
|
# initializer must not warn at all.
|
||||||
|
test.lint(fails=True,
|
||||||
|
verilator_flags2=['-Wwarn-MULTIDRIVENPROC'],
|
||||||
|
expect_filename=test.golden_filename)
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2026 Aisha Salimgereyeva
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
module t (
|
||||||
|
input wire clk
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clocking block declared before the drivers it contends with, so the
|
||||||
|
// conflict is found on reaching the design driver.
|
||||||
|
logic early_assign;
|
||||||
|
logic early_always;
|
||||||
|
|
||||||
|
clocking cb_early @(posedge clk);
|
||||||
|
output early_assign;
|
||||||
|
output early_always;
|
||||||
|
endclocking
|
||||||
|
|
||||||
|
assign early_assign = 1'b1;
|
||||||
|
always @(posedge clk) early_always <= 1'b1;
|
||||||
|
|
||||||
|
// Continuous assignment against a clocking block output: an unambiguous
|
||||||
|
// driver conflict, reported by MULTIDRIVEN.
|
||||||
|
logic late_assign;
|
||||||
|
assign late_assign = 1'b1;
|
||||||
|
|
||||||
|
// Plain always block against a clocking block output: a deliberate testbench
|
||||||
|
// idiom, so reported only by the off-by-default MULTIDRIVENPROC.
|
||||||
|
logic late_always;
|
||||||
|
always @(posedge clk) late_always <= 1'b1;
|
||||||
|
|
||||||
|
// Driven by two clocking blocks, reported by MULTIDRIVEN.
|
||||||
|
logic dual_clocking;
|
||||||
|
|
||||||
|
// Driven only by a clocking block: legal, must not warn.
|
||||||
|
logic clocking_only;
|
||||||
|
|
||||||
|
// Read by a clocking block rather than driven: legal, must not warn.
|
||||||
|
logic observed;
|
||||||
|
always_ff @(posedge clk) observed <= ~observed;
|
||||||
|
|
||||||
|
clocking cb_late @(posedge clk);
|
||||||
|
output late_assign;
|
||||||
|
output late_always;
|
||||||
|
output dual_clocking;
|
||||||
|
output clocking_only;
|
||||||
|
input observed;
|
||||||
|
endclocking
|
||||||
|
|
||||||
|
clocking cb_dual @(posedge clk);
|
||||||
|
output dual_clocking;
|
||||||
|
endclocking
|
||||||
|
|
||||||
|
// Declaration initializer alongside a clocking block output: the initializer
|
||||||
|
// is not a competing driver, must not warn.
|
||||||
|
logic decl_init = 1'b0;
|
||||||
|
|
||||||
|
// One signal named by two clockvars of the same clocking block: still a
|
||||||
|
// single driver, must not warn.
|
||||||
|
logic aliased;
|
||||||
|
|
||||||
|
clocking cb_alias @(posedge clk);
|
||||||
|
output decl_init;
|
||||||
|
output cv_a = aliased;
|
||||||
|
output cv_b = aliased;
|
||||||
|
endclocking
|
||||||
|
|
||||||
|
endmodule
|
||||||
Loading…
Reference in New Issue