Safely support non-overlapping blocking/non-blocking assignments (#6137)

The manual for the BLKANDNBLK warning describes that it is safe to
disable that error if the updated ranges are non-overlapping. This
however was not true (see the added t_nba_mixed_update* tests).

In this patch we change V3Delayed to use a new ShadowVarMasked
scheme for variables that have mixed blocking and non-blocking 
updates (or the FlagUnique scheme for unpacked variables), which
is in fact safe to use when the updated parts are non-overlapping.

Furthermore, mixed assignments are safe as far as scheduling is
concerned if either:

- They are to independent parts (bits/members/etc) (with this patch)
- Or if the blocking assignment is in clocked (or suspendable) logic.

The risk in scheduling is a race between the Post scheduled NBA
commit, and blocking assignments in combinational logic, which might
order incorrectly.

The second point highlights that we can handle stuff like this safely,
which is sometimes used in testbenches:

```systemverilog
always @(posedge clk) begin
    if ($time == 0) a = 0;
end

always @(posedge clk) begin
    if ($time > 0) a <= 2;
end
````

The only dangerous case is:

```systemverilog
always @(posedge clk) foo[idx] <= val;
assign foo[0] = bar;
```

Whit this patch, this will still resolve fine at run-time if 'idx' is
never zero, but might resolve incorrectly if 'idx' is zero.

With the above in mind, the BLKANDNBLK warning is now only issued if:

- We can't prove that the assignments are to non-overlapping bits
- And the blocking assignment is in combinational logic

These are the cases that genuinely require user attention to resolve.

With this patch, there are no more BLKANDNBLK warnings in the RTLMeter
designs.

Fixes #6122.
This commit is contained in:
Geza Lore 2025-06-28 20:45:45 +01:00 committed by GitHub
parent b914cda1c7
commit bc892deacc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 521 additions and 100 deletions

View File

@ -112,7 +112,7 @@ jobs:
- "VeeR-EL2:default*" - "VeeR-EL2:default*"
- "VeeR-EL2:hiperf*" - "VeeR-EL2:hiperf*"
- "Vortex:mini:*" - "Vortex:mini:*"
- "Vortex:sane:* !Vortex:sane:sgemm" - "Vortex:sane:*"
- "XiangShan:default-chisel3:* !*:linux" - "XiangShan:default-chisel3:* !*:linux"
- "XiangShan:default-chisel6:* !*:linux" - "XiangShan:default-chisel6:* !*:linux"
- "XiangShan:mini-chisel3:* !*:linux" - "XiangShan:mini-chisel3:* !*:linux"

View File

@ -211,23 +211,32 @@ List Of Warnings
public task, or when the blocking and non-blocking assignments have public task, or when the blocking and non-blocking assignments have
non-overlapping bits and structure members. non-overlapping bits and structure members.
From Verilator 5.038, this warning is only issued when Verilator can't prove that
the assignments are to non-overlapping sub-parts, and the blocking
assignment is in combinational logic (which is the case where simulation
results might differ from other simulators). Review any BLKANDNBLK
cases carefully after this version, and sign them off as
described above, only if know for sure the updates are not to overlapping
parts of the signal.
Generally, this is caused by a register driven by both combo logic and a Generally, this is caused by a register driven by both combo logic and a
flop: flop:
.. code-block:: sv .. code-block:: sv
logic [1:0] foo; logic [3:0] foo;
always @(posedge clk) foo[0] <= ... always @(posedge clk) foo[index] <= ... // With index != 0
always_comb foo[1] = ... always_comb foo[0] = ...
Instead, use a different register for the flop: Instead, use a different register for the flop:
.. code-block:: sv .. code-block:: sv
logic [1:0] foo; logic [3:0] foo;
always @(posedge clk) foo_flopped[0] <= ... logic [3:1] foo_flopped;
always_comb foo[0] = foo_flopped[0]; always @(posedge clk) foo_flopped[index] <= ... // With index != 0
always_comb foo[1] = ... always_comb foo[0] = ...
always_comb foo[3:1] = foo_flopped;
Or, this may also avoid the error: Or, this may also avoid the error:

View File

@ -1915,6 +1915,7 @@ class AstVar final : public AstNode {
bool m_isForcedByCode : 1; // May be forced/released from AstAssignForce/AstRelease bool m_isForcedByCode : 1; // May be forced/released from AstAssignForce/AstRelease
bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export
bool m_isWrittenBySuspendable : 1; // This variable can be written by a suspendable process bool m_isWrittenBySuspendable : 1; // This variable can be written by a suspendable process
bool m_ignorePostRead : 1; // Ignore reads in 'Post' blocks during ordering
bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations) bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
@ -1961,6 +1962,7 @@ class AstVar final : public AstNode {
m_isForcedByCode = false; m_isForcedByCode = false;
m_isWrittenByDpi = false; m_isWrittenByDpi = false;
m_isWrittenBySuspendable = false; m_isWrittenBySuspendable = false;
m_ignorePostRead = false;
m_ignorePostWrite = false; m_ignorePostWrite = false;
m_ignoreSchedWrite = false; m_ignoreSchedWrite = false;
m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN; m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN;
@ -2118,6 +2120,8 @@ public:
void setWrittenByDpi() { m_isWrittenByDpi = true; } void setWrittenByDpi() { m_isWrittenByDpi = true; }
bool isWrittenBySuspendable() const { return m_isWrittenBySuspendable; } bool isWrittenBySuspendable() const { return m_isWrittenBySuspendable; }
void setWrittenBySuspendable() { m_isWrittenBySuspendable = true; } void setWrittenBySuspendable() { m_isWrittenBySuspendable = true; }
bool ignorePostRead() const { return m_ignorePostRead; }
void setIgnorePostRead() { m_ignorePostRead = true; }
bool ignorePostWrite() const { return m_ignorePostWrite; } bool ignorePostWrite() const { return m_ignorePostWrite; }
void setIgnorePostWrite() { m_ignorePostWrite = true; } void setIgnorePostWrite() { m_ignorePostWrite = true; }
bool ignoreSchedWrite() const { return m_ignoreSchedWrite; } bool ignoreSchedWrite() const { return m_ignoreSchedWrite; }

View File

@ -71,6 +71,18 @@
// if (__VdlySet__LHS) a[__VdlyDim0__LHS][__VdlyDim1__LHS] = __VdlyVal__LHS; // if (__VdlySet__LHS) a[__VdlyDim0__LHS][__VdlyDim1__LHS] = __VdlyVal__LHS;
// Multiple consecutive NBAs of compatible form can share the same __VdlySet* flag // Multiple consecutive NBAs of compatible form can share the same __VdlySet* flag
// //
// "Shadow variable masked" scheme. Used for packed target variables that
// have both blocking and non-blocking updates. E.g.:
// LHS[Index] <= RHS;
// When there is also LHS[SomeNonOverlappingIndex] = RHS2;
// is converted to:
// - In the original logic, replace the AstAssignDelay with:
// __Vdly__LHS[Index] = RHS;
// __VdlyMask__LHS[Index] = '1;
// - Add new "Post-scheduled" logic:
// LHS = (__Vdly__LHS & __VdlyMask__LHS) | (LHS & ~__VdlyMask__LHS);
// __VdlyMask__LHS = '0;
//
// "Unique flag" scheme. Used for all variables updated by NBAs // "Unique flag" scheme. Used for all variables updated by NBAs
// in suspendable processees or forks. E.g.: // in suspendable processees or forks. E.g.:
// #1 LHS <= RHS; // #1 LHS <= RHS;
@ -124,6 +136,7 @@ class DelayedVisitor final : public VNVisitor {
Undecided = 0, Undecided = 0,
UnsupportedCompoundArrayInLoop, UnsupportedCompoundArrayInLoop,
ShadowVar, ShadowVar,
ShadowVarMasked,
FlagShared, FlagShared,
FlagUnique, FlagUnique,
ValueQueueWhole, ValueQueueWhole,
@ -152,6 +165,10 @@ class DelayedVisitor final : public VNVisitor {
struct { // Stuff needed for Scheme::ShadowVar struct { // Stuff needed for Scheme::ShadowVar
AstVarScope* vscp; // The shadow variable AstVarScope* vscp; // The shadow variable
} m_shadowVariableKit; } m_shadowVariableKit;
struct { // Stuff needed for Scheme::ShadowVarMasked
AstVarScope* vscp; // The shadow variable
AstVarScope* maskp; // The mask variable
} m_shadowVarMaskedKit;
struct { // Stuff needed for Scheme::FlagShared struct { // Stuff needed for Scheme::FlagShared
AstActive* activep; // The active block for the Pre/Post logic AstActive* activep; // The active block for the Pre/Post logic
AstAlwaysPost* postp; // The post block for commiting results AstAlwaysPost* postp; // The post block for commiting results
@ -174,6 +191,10 @@ class DelayedVisitor final : public VNVisitor {
UASSERT(m_scheme == Scheme::ShadowVar, "Inconsistent Scheme"); UASSERT(m_scheme == Scheme::ShadowVar, "Inconsistent Scheme");
return m_kitUnion.m_shadowVariableKit; return m_kitUnion.m_shadowVariableKit;
} }
auto& shadowVarMaskedKit() {
UASSERT(m_scheme == Scheme::ShadowVarMasked, "Inconsistent Scheme");
return m_kitUnion.m_shadowVarMaskedKit;
}
auto& flagSharedKit() { auto& flagSharedKit() {
UASSERT(m_scheme == Scheme::FlagShared, "Inconsistent Scheme"); UASSERT(m_scheme == Scheme::FlagShared, "Inconsistent Scheme");
return m_kitUnion.m_flagSharedKit; return m_kitUnion.m_flagSharedKit;
@ -202,6 +223,18 @@ class DelayedVisitor final : public VNVisitor {
void addSensitivity(AstSenTree* nodep) { addSensitivity(nodep->sensesp()); } void addSensitivity(AstSenTree* nodep) { addSensitivity(nodep->sensesp()); }
}; };
// Data structure to keep track of all writes to
struct WriteReference final {
AstVarRef* m_refp = nullptr; // The reference
bool m_isNBA = false; // True if an NBA write
bool m_inNonComb = false; // True if reference is known to be in non-combinational logic
WriteReference() = default;
WriteReference(AstVarRef* refp, bool isNBA, bool inNonComb)
: m_refp{refp}
, m_isNBA{isNBA}
, m_inNonComb{inNonComb} {}
};
// Data required to lower AstAssignDelay later // Data required to lower AstAssignDelay later
struct NBA final { struct NBA final {
AstAssignDly* nodep = nullptr; // The NBA this record refers to AstAssignDly* nodep = nullptr; // The NBA this record refers to
@ -215,10 +248,13 @@ class DelayedVisitor final : public VNVisitor {
// AstNodeModule::user1p() -> std::unorded_map<std::string, AstVar*> temp map via m_varMap // AstNodeModule::user1p() -> std::unorded_map<std::string, AstVar*> temp map via m_varMap
// AstVarScope::user1p() -> VarScopeInfo via m_vscpInfo // AstVarScope::user1p() -> VarScopeInfo via m_vscpInfo
// AstVarScope::user2p() -> AstVarRef*: First write reference to the Variable // AstVarScope::user2p() -> AstVarRef*: First write reference to the Variable
// AstVarScope::user3p() -> std::vector<WriteReference> via m_writeRefs;
const VNUser1InUse m_user1InUse{}; const VNUser1InUse m_user1InUse{};
const VNUser2InUse m_user2InUse{}; const VNUser2InUse m_user2InUse{};
const VNUser3InUse m_user3InUse{};
AstUser1Allocator<AstNodeModule, std::unordered_map<std::string, AstVar*>> m_varMap; AstUser1Allocator<AstNodeModule, std::unordered_map<std::string, AstVar*>> m_varMap;
AstUser1Allocator<AstVarScope, VarScopeInfo> m_vscpInfo; AstUser1Allocator<AstVarScope, VarScopeInfo> m_vscpInfo;
AstUser3Allocator<AstVarScope, std::vector<WriteReference>> m_writeRefs;
// STATE - across all visitors // STATE - across all visitors
std::set<AstSenTree*> m_timingDomains; // Timing resume domains std::set<AstSenTree*> m_timingDomains; // Timing resume domains
@ -230,6 +266,7 @@ class DelayedVisitor final : public VNVisitor {
bool m_inLoop = false; // True in for loops bool m_inLoop = false; // True in for loops
bool m_inSuspendableOrFork = false; // True in suspendable processes and forks bool m_inSuspendableOrFork = false; // True in suspendable processes and forks
bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK
bool m_inNonCombLogic = false; // We are in non-combinational logic
AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference
// STATE - during NBA conversion (after visit) // STATE - during NBA conversion (after visit)
@ -240,6 +277,7 @@ class DelayedVisitor final : public VNVisitor {
// STATE - Statistic tracking // STATE - Statistic tracking
VDouble0 m_nSchemeShadowVar; // Number of variables using Scheme::ShadowVar VDouble0 m_nSchemeShadowVar; // Number of variables using Scheme::ShadowVar
VDouble0 m_nSchemeShadowVarMasked; // Number of variabels using Scheme::ShadowVarMasked
VDouble0 m_nSchemeFlagShared; // Number of variables using Scheme::FlagShared VDouble0 m_nSchemeFlagShared; // Number of variables using Scheme::FlagShared
VDouble0 m_nSchemeFlagUnique; // Number of variables using Scheme::FlagUnique VDouble0 m_nSchemeFlagUnique; // Number of variables using Scheme::FlagUnique
VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole
@ -248,15 +286,129 @@ class DelayedVisitor final : public VNVisitor {
// METHODS // METHODS
// Return true iff a variable is assigned by both blocking and nonblocking
// assignments. Issue BLKANDNBLK error if we can't prove the mixed
// assignments are to independent bits and the blocking assignment can be
// in combinational logic, which is something we can't safely implement
// still.
bool checkMixedUsage(const AstVarScope* vscp, bool isIntegralOrPacked) {
struct Ref final {
AstVarRef* m_refp; // The reference
bool m_inNonComb; // True if known to be in non-combinational logic
int m_lsb; // LSB of accessed range
int m_msb; // MSB of accessed range
Ref(AstVarRef* refp, bool inNonComb, int lsb, int msb)
: m_refp{refp}
, m_inNonComb{inNonComb}
, m_lsb{lsb}
, m_msb{msb} {}
};
std::vector<Ref> blkRefs; // Blocking writes
std::vector<Ref> nbaRefs; // Non-blockign writes
const int width = isIntegralOrPacked ? vscp->width() : 1;
for (const auto& writeRef : m_writeRefs(vscp)) {
int lsb = 0;
int msb = width - 1;
if (const AstSel* const selp = VN_CAST(writeRef.m_refp->backp(), Sel)) {
if (VN_IS(selp->lsbp(), Const)) {
lsb = selp->lsbConst();
msb = selp->msbConst();
}
}
if (writeRef.m_isNBA) {
nbaRefs.emplace_back(writeRef.m_refp, writeRef.m_inNonComb, lsb, msb);
} else {
blkRefs.emplace_back(writeRef.m_refp, writeRef.m_inNonComb, lsb, msb);
}
}
// We only run this function on targets of NBAs, so there should be at least one...
UASSERT_OBJ(!nbaRefs.empty(), vscp, "Did not record NBA write");
// If no blocking upadte, then we are good
if (blkRefs.empty()) return false;
// If the blocking assignment is in non-combinational logic (i.e.:
// in logic that has an explicit trigger), then we can safely
// implement it (there is no race between clocked logic and post
// scheduled logic), so need not error
blkRefs.erase(std::remove_if(blkRefs.begin(), blkRefs.end(),
[](const Ref& ref) { return ref.m_inNonComb; }),
blkRefs.end());
// If nothing left, then we need not error
if (blkRefs.empty()) return true;
// If not a packed variable, warn here as we can't prove independence
if (!isIntegralOrPacked) {
const Ref& blkRef = blkRefs.front();
const Ref& nbaRef = nbaRefs.front();
vscp->v3warn(
BLKANDNBLK,
"Unsupported: Blocking and non-blocking assignments to same non-packed variable: "
<< vscp->varp()->prettyNameQ() << '\n'
<< vscp->warnContextPrimary() << '\n'
<< blkRef.m_refp->warnOther() << "... Location of blocking assignment\n"
<< blkRef.m_refp->warnContextSecondary() << '\n'
<< nbaRef.m_refp->warnOther() << "... Location of nonblocking assignment\n"
<< nbaRef.m_refp->warnContextSecondary());
return true;
}
// We need to error if we can't prove the written bits are independent
// Sort refs by interval
const auto lessThanRef = [](const Ref& a, const Ref& b) {
if (a.m_lsb != b.m_lsb) return a.m_lsb < b.m_lsb;
return a.m_msb < b.m_msb;
};
std::stable_sort(blkRefs.begin(), blkRefs.end(), lessThanRef);
std::stable_sort(nbaRefs.begin(), nbaRefs.end(), lessThanRef);
// Iterate both vectors, checking for overlap
auto bIt = blkRefs.begin();
auto nIt = nbaRefs.begin();
while (bIt != blkRefs.end() && nIt != nbaRefs.end()) {
if (lessThanRef(*bIt, *nIt)) {
if (nIt->m_lsb <= bIt->m_msb) break; // Stop on Overlap
++bIt;
} else {
if (bIt->m_lsb <= nIt->m_msb) break; // Stop on Overlap
++nIt;
}
}
// If we found an overlapping range that cannot be safely implemented, then wran...
if (bIt != blkRefs.end() && nIt != nbaRefs.end()) {
const Ref& blkRef = *bIt;
const Ref& nbaRef = *nIt;
vscp->v3warn(BLKANDNBLK,
"Unsupported: Blocking and non-blocking assignments to "
"potentially overlapping bits of same packed variable: "
<< vscp->varp()->prettyNameQ() << '\n'
<< vscp->warnContextPrimary() << '\n'
<< blkRef.m_refp->warnOther() << "... Location of blocking assignment"
<< " (bits [" << blkRef.m_msb << ":" << blkRef.m_lsb << "])\n"
<< blkRef.m_refp->warnContextSecondary() << '\n'
<< nbaRef.m_refp->warnOther()
<< "... Location of nonblocking assignment"
<< " (bits [" << nbaRef.m_msb << ":" << nbaRef.m_lsb << "])\n"
<< nbaRef.m_refp->warnContextSecondary());
}
return true;
}
// Choose the NBA scheme used for the given variable. // Choose the NBA scheme used for the given variable.
static Scheme chooseScheme(const AstVarScope* vscp, const VarScopeInfo& vscpInfo) { Scheme chooseScheme(const AstVarScope* vscp, const VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::Undecided, vscp, "NBA scheme already decided"); UASSERT_OBJ(vscpInfo.m_scheme == Scheme::Undecided, vscp, "NBA scheme already decided");
const AstNodeDType* const dtypep = vscp->dtypep()->skipRefp(); const AstNodeDType* const dtypep = vscp->dtypep()->skipRefp();
// Unpacked arrays // Unpacked arrays
if (const AstUnpackArrayDType* const uaDTypep = VN_CAST(dtypep, UnpackArrayDType)) { if (const AstUnpackArrayDType* const uaDTypep = VN_CAST(dtypep, UnpackArrayDType)) {
// Basic underlying type of elements, if any. // Basic underlying type of elements, if any.
AstBasicDType* const basicp = uaDTypep->basicp(); const AstBasicDType* const basicp = uaDTypep->basicp();
// If used in a loop, we must have a dynamic commit queue. (Also works in suspendables) // If used in a loop, we must have a dynamic commit queue. (Also works in suspendables)
if (vscpInfo.m_inLoop) { if (vscpInfo.m_inLoop) {
// Arrays with compound element types are currently not supported in loops // Arrays with compound element types are currently not supported in loops
@ -281,6 +433,25 @@ class DelayedVisitor final : public VNVisitor {
// In a suspendable of fork, we must use the unique flag scheme, TODO: why? // In a suspendable of fork, we must use the unique flag scheme, TODO: why?
if (vscpInfo.m_inSuspOrFork) return Scheme::FlagUnique; if (vscpInfo.m_inSuspOrFork) return Scheme::FlagUnique;
const bool isIntegralOrPacked = dtypep->isIntegralOrPacked();
// Check for mixed usage (this also warns if not OK)
if (checkMixedUsage(vscp, isIntegralOrPacked)) {
// If it's a variable updated by both blocking and non-blocking
// asignments, use the ShadowVarMasked schem if masked update is
// possible. This can handle blocking and non-blocking updates to
// inpdendent parts correctly at run-time, and always works, even
// in loops or other dynamic context.
if (isIntegralOrPacked) return Scheme::ShadowVarMasked;
// If it's inside a loop, use Scheme::ShadowVar, which is safe,
// but will generate incorrect code if a partial update is used
if (vscpInfo.m_inLoop) return Scheme::ShadowVar;
// Otherwise (for not packed variables), use the FlagUnique scheme,
// which at least handles partial updates correctly, but might break
// in loops or other dynamic context
return Scheme::FlagUnique;
}
// Otherwise use the simple shadow variable scheme // Otherwise use the simple shadow variable scheme
return Scheme::ShadowVar; return Scheme::ShadowVar;
} }
@ -350,7 +521,12 @@ class DelayedVisitor final : public VNVisitor {
nodep = arrSelp->fromp(); nodep = arrSelp->fromp();
} }
// What remains must be an AstVarRef, or some sort of select, we assume can reuse it. // What remains must be an AstVarRef, or some sort of select, we assume can reuse it.
UASSERT_OBJ(nodep->isPure(), lhsp, "Malformed LHS in NBA"); if (const AstAssocSel* const aselp = VN_CAST(nodep, AssocSel)) {
UASSERT_OBJ(aselp->fromp()->isPure() && aselp->bitp()->isPure(), lhsp,
"Malformed LHS in NBA");
} else {
UASSERT_OBJ(nodep->isPure(), lhsp, "Malformed LHS in NBA");
}
// Now have been converted to use the captured values // Now have been converted to use the captured values
return lhsp; return lhsp;
} }
@ -410,6 +586,70 @@ class DelayedVisitor final : public VNVisitor {
}); });
} }
// Scheme::ShadowVarMasked
void prepareSchemeShadowVarMasked(AstVarScope* vscp, VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::ShadowVarMasked, vscp, "Inconsistent NBA scheme");
FileLine* const flp = vscp->fileline();
AstScope* const scopep = vscp->scopep();
// Create the shadow variable
const std::string shadowName = "__Vdly__" + vscp->varp()->shortName();
AstVarScope* const shadowVscp = createTemp(flp, scopep, shadowName, vscp->dtypep());
vscpInfo.shadowVarMaskedKit().vscp = shadowVscp;
// Create the makk variable
const std::string maskName = "__VdlyMask__" + vscp->varp()->shortName();
AstVarScope* const maskVscp = createTemp(flp, scopep, maskName, vscp->dtypep());
maskVscp->varp()->setIgnorePostWrite();
vscpInfo.shadowVarMaskedKit().maskp = maskVscp;
// Create the AstActive for the Post logic
AstActive* const activep
= new AstActive{flp, "nba-shadow-var-masked", vscpInfo.senTreep()};
activep->sensesStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Post' scheduled process for the commit and mask clear
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
activep->addStmtsp(postp);
// Add the commit - vscp = (shadowVscp & maskVscp) | (vscp & ~maskVscp);
postp->addStmtsp(new AstAssign{
flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstOr{flp,
new AstAnd{flp, new AstVarRef{flp, shadowVscp, VAccess::READ},
new AstVarRef{flp, maskVscp, VAccess::READ}},
new AstAnd{flp, new AstVarRef{flp, vscp, VAccess::READ},
new AstNot{flp, new AstVarRef{flp, maskVscp, VAccess::READ}}}}});
vscp->varp()->setIgnorePostRead();
// Clar the mask - maskVscp = '0;
postp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, maskVscp, VAccess::WRITE},
new AstConst{flp, AstConst::WidthedValue{}, maskVscp->width(), 0}});
}
void convertSchemeShadowVarMasked(AstAssignDly* nodep, AstVarScope* vscp,
VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::ShadowVarMasked, vscp, "Inconsistent NBA scheme");
AstVarScope* const shadowVscp = vscpInfo.shadowVarMaskedKit().vscp;
AstVarScope* const maskVscp = vscpInfo.shadowVarMaskedKit().maskp;
AstNodeExpr* lhsClonep = nodep->lhsp()->cloneTree(false);
// Replace the write ref on the LHS with the shadow variable
nodep->lhsp()->foreach([&](AstVarRef* const refp) {
if (!refp->access().isWriteOnly()) return;
UASSERT_OBJ(refp->varScopep() == vscp, nodep, "NBA not setting expected variable");
refp->varScopep(shadowVscp);
refp->varp(shadowVscp->varp());
});
// Set the same bits in the mask to 1
lhsClonep->foreach([&](AstVarRef* const refp) {
if (!refp->access().isWriteOnly()) return;
UASSERT_OBJ(refp->varScopep() == vscp, nodep, "NBA not setting expected variable");
refp->varScopep(maskVscp);
refp->varp(maskVscp->varp());
});
FileLine* const flp = nodep->fileline();
AstConst* const onesp = new AstConst{flp, AstConst::DTyped{}, lhsClonep->dtypep()};
onesp->num().setAllBits1();
nodep->addNextHere(new AstAssign{flp, lhsClonep, onesp});
}
// Scheme::FlagShared // Scheme::FlagShared
void prepareSchemeFlagShared(AstVarScope* vscp, VarScopeInfo& vscpInfo) { void prepareSchemeFlagShared(AstVarScope* vscp, VarScopeInfo& vscpInfo) {
UASSERT_OBJ(vscpInfo.m_scheme == Scheme::FlagShared, vscp, "Inconsistent NBA scheme"); UASSERT_OBJ(vscpInfo.m_scheme == Scheme::FlagShared, vscp, "Inconsistent NBA scheme");
@ -705,51 +945,19 @@ class DelayedVisitor final : public VNVisitor {
pushDeletep(nodep->unlinkFrBack()); pushDeletep(nodep->unlinkFrBack());
} }
// Record and warn if a variable is assigned by both blocking and nonblocking assignments // Record where a variable is assigned
void checkVarUsage(AstVarRef* nodep, bool nonBlocking) { void recordWriteRef(AstVarRef* nodep, bool nonBlocking) {
// Ignore references in certain contexts // Ignore references in certain contexts
if (m_ignoreBlkAndNBlk) return; if (m_ignoreBlkAndNBlk) return;
// Ignore if warning is disabled on this reference (used by V3Force).
if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return;
// Ignore if it's an array // Ignore if it's an array
// TODO: we do this because it used to be the previous behaviour. // TODO: we do this because it used to be the previous behaviour.
// Is it still required, or should we warn for arrays as well? // Is it still required, or should we warn for arrays as well?
// Scheduling is no different for them... // Scheduling is no different for them...
// Clarification: This is OK for arrays of primitive types, but
// arrays that use the ShadowVar scheme don't work...
if (VN_IS(nodep->varScopep()->dtypep()->skipRefp(), UnpackArrayDType)) return; if (VN_IS(nodep->varScopep()->dtypep()->skipRefp(), UnpackArrayDType)) return;
// Mark ref as blocking/non-blocking m_writeRefs(nodep->varScopep()).emplace_back(nodep, nonBlocking, m_inNonCombLogic);
nodep->user1(nonBlocking);
AstVarScope* const vscp = nodep->varScopep();
// Pick up/set the first reference to this variable
const AstVarRef* const firstRefp = VN_AS(vscp->user2p(), VarRef);
if (!firstRefp) {
vscp->user2p(nodep);
return;
}
// If both blocking/non-blocking, it's OK
if (firstRefp->user1() == static_cast<int>(nonBlocking)) return;
// Otherwise warn that both blocking and non-blocking assignments are used
const auto containingAssignment = [](const AstNode* nodep) -> const AstNode* {
while (!VN_IS(nodep, NodeAssign)) nodep = nodep->backp();
return nodep;
};
const AstNode* nonblockingp = nonBlocking ? nodep : firstRefp;
if (const AstNode* np = containingAssignment(nonblockingp)) nonblockingp = np;
const AstNode* blockingp = nonBlocking ? firstRefp : nodep;
if (const AstNode* np = containingAssignment(blockingp)) blockingp = np;
vscp->v3warn(BLKANDNBLK,
"Unsupported: Blocked and non-blocking assignments to same variable: "
<< vscp->varp()->prettyNameQ() << '\n'
<< vscp->warnContextPrimary() << '\n'
<< blockingp->warnOther() << "... Location of blocking assignment\n"
<< blockingp->warnContextSecondary() << '\n'
<< nonblockingp->warnOther() << "... Location of nonblocking assignment\n"
<< nonblockingp->warnContextSecondary());
} }
// VISITORS // VISITORS
@ -773,6 +981,11 @@ class DelayedVisitor final : public VNVisitor {
prepareSchemeShadowVar(vscp, vscpInfo); prepareSchemeShadowVar(vscp, vscpInfo);
break; break;
} }
case Scheme::ShadowVarMasked: {
++m_nSchemeShadowVarMasked;
prepareSchemeShadowVarMasked(vscp, vscpInfo);
break;
}
case Scheme::FlagShared: { case Scheme::FlagShared: {
++m_nSchemeFlagShared; ++m_nSchemeFlagShared;
prepareSchemeFlagShared(vscp, vscpInfo); prepareSchemeFlagShared(vscp, vscpInfo);
@ -816,6 +1029,10 @@ class DelayedVisitor final : public VNVisitor {
convertSchemeShadowVar(nbap, vscp, vscpInfo); convertSchemeShadowVar(nbap, vscp, vscpInfo);
break; break;
} }
case Scheme::ShadowVarMasked: {
convertSchemeShadowVarMasked(nbap, vscp, vscpInfo);
break;
}
case Scheme::FlagShared: { case Scheme::FlagShared: {
convertSchemeFlagShared(nbap, vscp, vscpInfo); convertSchemeFlagShared(nbap, vscp, vscpInfo);
break; break;
@ -844,9 +1061,11 @@ class DelayedVisitor final : public VNVisitor {
UASSERT_OBJ(!m_activep, nodep, "Should not nest"); UASSERT_OBJ(!m_activep, nodep, "Should not nest");
VL_RESTORER(m_activep); VL_RESTORER(m_activep);
VL_RESTORER(m_ignoreBlkAndNBlk); VL_RESTORER(m_ignoreBlkAndNBlk);
VL_RESTORER(m_inNonCombLogic);
m_activep = nodep; m_activep = nodep;
AstSenTree* const senTreep = nodep->sensesp(); AstSenTree* const senTreep = nodep->sensesp();
m_ignoreBlkAndNBlk = senTreep->hasStatic() || senTreep->hasInitial(); m_ignoreBlkAndNBlk = senTreep->hasStatic() || senTreep->hasInitial();
m_inNonCombLogic = senTreep->hasClocked();
iterateChildren(nodep); iterateChildren(nodep);
} }
void visit(AstNodeProcedure* nodep) override { void visit(AstNodeProcedure* nodep) override {
@ -854,8 +1073,14 @@ class DelayedVisitor final : public VNVisitor {
{ {
VL_RESTORER(m_inSuspendableOrFork); VL_RESTORER(m_inSuspendableOrFork);
VL_RESTORER(m_procp); VL_RESTORER(m_procp);
VL_RESTORER(m_ignoreBlkAndNBlk);
VL_RESTORER(m_inNonCombLogic);
m_inSuspendableOrFork = nodep->isSuspendable(); m_inSuspendableOrFork = nodep->isSuspendable();
m_procp = nodep; m_procp = nodep;
if (m_inSuspendableOrFork) {
m_ignoreBlkAndNBlk = false;
m_inNonCombLogic = true;
}
iterateChildren(nodep); iterateChildren(nodep);
} }
if (m_timingDomains.empty()) return; if (m_timingDomains.empty()) return;
@ -1013,8 +1238,8 @@ class DelayedVisitor final : public VNVisitor {
nba.nodep = nodep; nba.nodep = nodep;
nba.vscp = vscp; nba.vscp = vscp;
// Check var usage // Record write reference
checkVarUsage(m_currNbaLhsRefp, true); recordWriteRef(m_currNbaLhsRefp, true);
iterateChildren(nodep); iterateChildren(nodep);
} }
@ -1023,14 +1248,8 @@ class DelayedVisitor final : public VNVisitor {
if (nodep == m_currNbaLhsRefp) return; if (nodep == m_currNbaLhsRefp) return;
// Only care about write refs // Only care about write refs
if (!nodep->access().isWriteOrRW()) return; if (!nodep->access().isWriteOrRW()) return;
// Check var usage // Record write reference
checkVarUsage(nodep, false); recordWriteRef(nodep, false);
}
void visit(AstNodeReadWriteMem* nodep) override {
VL_RESTORER(m_ignoreBlkAndNBlk);
// $readmem/$writemem often used in mem models so we suppress BLKANDNBLK warnings
m_ignoreBlkAndNBlk = true;
iterateChildren(nodep);
} }
void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc("For statements should have been converted to while statements"); nodep->v3fatalSrc("For statements should have been converted to while statements");
@ -1054,6 +1273,7 @@ public:
explicit DelayedVisitor(AstNetlist* nodep) { iterate(nodep); } explicit DelayedVisitor(AstNetlist* nodep) { iterate(nodep); }
~DelayedVisitor() override { ~DelayedVisitor() override {
V3Stats::addStat("NBA, variables using ShadowVar scheme", m_nSchemeShadowVar); V3Stats::addStat("NBA, variables using ShadowVar scheme", m_nSchemeShadowVar);
V3Stats::addStat("NBA, variables using ShadowVarMasked scheme", m_nSchemeShadowVarMasked);
V3Stats::addStat("NBA, variables using FlagShared scheme", m_nSchemeFlagShared); V3Stats::addStat("NBA, variables using FlagShared scheme", m_nSchemeFlagShared);
V3Stats::addStat("NBA, variables using FlagUnique scheme", m_nSchemeFlagUnique); V3Stats::addStat("NBA, variables using FlagUnique scheme", m_nSchemeFlagUnique);
V3Stats::addStat("NBA, variables using ValueQueueWhole scheme", m_nSchemeValueQueuesWhole); V3Stats::addStat("NBA, variables using ValueQueueWhole scheme", m_nSchemeValueQueuesWhole);

View File

@ -272,10 +272,8 @@ class ForceConvertVisitor final : public VNVisitor {
// it is a variable, and not a net, set the original signal to the forced value, as it // it is a variable, and not a net, set the original signal to the forced value, as it
// needs to retain the forced value until the next procedural update, which might happen on // needs to retain the forced value until the next procedural update, which might happen on
// a later eval. Luckily we can do all this in a single assignment. // a later eval. Luckily we can do all this in a single assignment.
FileLine* const fl_nowarn = new FileLine{flp};
fl_nowarn->warnOff(V3ErrorCode::BLKANDNBLK, true);
AstAssign* const resetRdp AstAssign* const resetRdp
= new AstAssign{fl_nowarn, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()}; = new AstAssign{flp, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()};
// Replace write refs on the LHS // Replace write refs on the LHS
resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) { resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) {
if (refp->access() != VAccess::WRITE) return; if (refp->access() != VAccess::WRITE) return;
@ -283,10 +281,7 @@ class ForceConvertVisitor final : public VNVisitor {
AstVarScope* const newVscp = vscp->varp()->isContinuously() AstVarScope* const newVscp = vscp->varp()->isContinuously()
? m_state.getForceComponents(vscp).m_rdVscp ? m_state.getForceComponents(vscp).m_rdVscp
: vscp; : vscp;
// Disable BLKANDNBLK for this reference AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), newVscp, VAccess::WRITE};
FileLine* const flp = new FileLine{refp->fileline()};
flp->warnOff(V3ErrorCode::BLKANDNBLK, true);
AstVarRef* const newpRefp = new AstVarRef{flp, newVscp, VAccess::WRITE};
refp->replaceWith(newpRefp); refp->replaceWith(newpRefp);
VL_DO_DANGLING(refp->deleteTree(), refp); VL_DO_DANGLING(refp->deleteTree(), refp);
}); });

View File

@ -254,8 +254,15 @@ class OrderGraphBuilder final : public VNVisitor {
// Update VarUsage // Update VarUsage
varscp->user2(varscp->user2() | VU_CON); varscp->user2(varscp->user2() | VU_CON);
// Add edges // Add edges
if (!m_inClocked || m_inPost) { if (m_inPost) {
// Combinational logic // Combinational logic
if (!varscp->varp()->ignorePostRead() && m_readTriggersCombLogic(varscp)) {
// Ignore explicit sensitivities
OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
// Add edge from consumed VarStdVertex -> to consuming LogicVertex
m_graphp->addHardEdge(varVxp, m_logicVxp, WEIGHT_MEDIUM);
}
} else if (!m_inClocked) { // Combinational logic
if (m_readTriggersCombLogic(varscp)) { if (m_readTriggersCombLogic(varscp)) {
// Ignore explicit sensitivities // Ignore explicit sensitivities
OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);

View File

@ -46,7 +46,6 @@
//END_MODULE_NAME-------------------------------------------------------------- //END_MODULE_NAME--------------------------------------------------------------
//See also: https://github.com/twosigma/verilator_support //See also: https://github.com/twosigma/verilator_support
// verilator lint_off BLKANDNBLK
// verilator lint_off COMBDLY // verilator lint_off COMBDLY
// verilator lint_off INITIALDLY // verilator lint_off INITIALDLY
// verilator lint_off MULTIDRIVEN // verilator lint_off MULTIDRIVEN

View File

@ -1,11 +0,0 @@
%Error-BLKANDNBLK: t/t_assign_on_rhs_of_nonblocking_unsup.v:15:8: Unsupported: Blocked and non-blocking assignments to same variable: 't.x'
15 | int x;
| ^
t/t_assign_on_rhs_of_nonblocking_unsup.v:24:18: ... Location of blocking assignment
24 | y <= (x = 2);
| ^
t/t_assign_on_rhs_of_nonblocking_unsup.v:21:12: ... Location of nonblocking assignment
21 | x <= 1;
| ^~
... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest
%Error: Exiting due to

View File

@ -49,21 +49,15 @@ module t (/*AUTOARG*/
end end
// //
else if (cyc == 10) begin else if (cyc == 10) begin
/* verilator lint_off BLKANDNBLK */
i_cast <= $cast(e, 60'h1234); i_cast <= $cast(e, 60'h1234);
/* verilator lint_on BLKANDNBLK */
end end
else if (cyc == 11) begin else if (cyc == 11) begin
`checkh(i_cast, 0); `checkh(i_cast, 0);
/* verilator lint_off BLKANDNBLK */
i_cast <= $cast(e, 60'h1); i_cast <= $cast(e, 60'h1);
/* verilator lint_on BLKANDNBLK */
end end
else if (cyc == 12) begin else if (cyc == 12) begin
`checkh(i_cast, 1); `checkh(i_cast, 1);
/* verilator lint_off BLKANDNBLK */
i_cast <= $cast(e, 60'h1234_4567_abcd); i_cast <= $cast(e, 60'h1234_4567_abcd);
/* verilator lint_on BLKANDNBLK */
end end
else if (cyc == 13) begin else if (cyc == 13) begin
`checkh(i_cast, 1); `checkh(i_cast, 1);

View File

@ -114,7 +114,7 @@ module t(/*AUTOARG*/
release r; release r;
end end
else if (cyc == 45) begin else if (cyc == 45) begin
`checkr(r, 1.25); `checkr(r, 2.5);
end end
// //
else if (cyc == 99) begin else if (cyc == 99) begin

View File

@ -12,11 +12,9 @@ module t (/*AUTOARG*/
input clk; input clk;
// verilator lint_off ASCRANGE // verilator lint_off ASCRANGE
// verilator lint_off BLKANDNBLK
// 3 3 4 // 3 3 4
reg [71:0] memw [2:0][1:3][5:2]; reg [71:0] memw [2:0][1:3][5:2];
reg [7:0] memn [2:0][1:3][5:2]; reg [7:0] memn [2:0][1:3][5:2];
// verilator lint_on BLKANDNBLK
integer cyc; initial cyc = 0; integer cyc; initial cyc = 0;
reg [63:0] crc; reg [63:0] crc;

View File

@ -9,8 +9,10 @@
import vltest_bootstrap import vltest_bootstrap
test.scenarios('linter') test.scenarios('vlt')
test.lint(fails=True, expect_filename=test.golden_filename) test.compile()
test.execute()
test.passes() test.passes()

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=["--stats", "--unroll-count", "1"])
test.execute()
test.file_grep(test.stats, r'NBA, variables using ShadowVar scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'NBA, variables using ShadowVarMasked scheme\s+(\d+)', 2)
test.file_grep(test.stats, r'NBA, variables using FlagUnique scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 0)
test.file_grep_not(test.stats, r'Warnings, Suppressed BLKANDNBLK')
test.passes()

View File

@ -0,0 +1,59 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define check(got ,exp) do if ((got) !== (exp)) begin $write("%%Error: %s:%0d: cyc=%0d got='h%x exp='h%x\n", `__FILE__,`__LINE__, cyc, (got), (exp)); `stop; end while(0)
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
reg [31:0] cyc = 0;
// 'x' has both blocking and non-blocking update, with the blocking
// update in **clocked** logic
reg [1:0] x = 2'b00;
// '{y1, y0}' should have exactly the same value as 'x', at all times
reg y0 = 1'b0;
reg y1 = 1'b0;
// 'z[0]' should equal '{8{x[0]}', 'z[1]' should equal '{8{x[1]}}'
reg [1:0][7:0] z;
// 'pair.a' should equal 'x[0]', 'pair.b' should equal 'x[1]'
struct {
logic a;
logic b;
} pair = '{a: 1'b0, b: 1'b0};
always @(posedge clk) begin
$display("cyc = %d (%08x) x[1] = %0d, x[0] = %0d, y1 = %0d, y0 = %0d z[1] = %02x z[1] = %02x pair.a = %0d pair.b = %0d",
cyc, cyc, x[1], x[0], y1, y0, z[1], z[0], pair.a, pair.b);
`check(x[0], cyc[0]);
`check(x[1], cyc[0]);
`check(y0, cyc[0]);
`check(y1, cyc[0]);
`check(z[0], {8{cyc[0]}});
`check(z[1], {8{cyc[0]}});
`check(pair.a, cyc[0]);
`check(pair.b, cyc[0]);
x[1] <= ~x[1];
y1 <= ~y1;
for (int i = 0; i < 8; ++i) z[1][i] <= ~z[1][i];
pair.b <= ~pair.b;
cyc = cyc + 1;
x[0] = cyc[0];
y0 = cyc[0];
for (int i = 0; i < 8; ++i) z[0][i] = cyc[0];
pair.a = cyc[0];
if (cyc == 99) begin
$display(x);
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=["--stats", "--unroll-count", "1"])
test.execute()
test.file_grep(test.stats, r'NBA, variables using ShadowVar scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'NBA, variables using ShadowVarMasked scheme\s+(\d+)', 2)
test.file_grep(test.stats, r'NBA, variables using FlagUnique scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 0)
test.file_grep(test.stats, r'Warnings, Suppressed BLKANDNBLK\s+(\d+)', 2)
test.passes()

View File

@ -0,0 +1,72 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define check(got ,exp) do if ((got) !== (exp)) begin $write("%%Error: %s:%0d: cyc=%0d got='h%x exp='h%x\n", `__FILE__,`__LINE__, cyc, (got), (exp)); `stop; end while(0)
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
reg [31:0] cyc = 0;
reg [31:0] sameAsCycButCantBeOptimized_0;
reg [31:0] sameAsCycButCantBeOptimized_1;
reg [31:0] sameAsCycButCantBeOptimized_2;
// 'x' has both blocking and non-blocking update, with the blocking
// update in **combinational** logic
reg [1:0] x = 2'b00;
// '{y1, y0}' should have exactly the same value as 'x', at all times
reg y0 = 1'b0;
reg y1 = 1'b0;
// 'z[0]' should equal '{8{x[0]}', 'z[1]' should equal '{8{x[1]}}'
// verilator lint_off BLKANDNBLK
reg [1:0][7:0] z;
// verilator lint_on BLKANDNBLK
// 'pair.a' should equal 'x[0]', 'pair.b' should equal 'x[1]'
// verilator lint_off BLKANDNBLK
struct {
logic a;
logic b;
} pair = '{a: 1'b0, b: 1'b0};
// verilator lint_on BLKANDNBLK
assign x[0] = cyc[0];
assign y0 = sameAsCycButCantBeOptimized_0[0];
always_comb begin
for (int i = 0; i < 8; ++i) z[0][i] = sameAsCycButCantBeOptimized_1[0];
end
assign pair.a = sameAsCycButCantBeOptimized_2[0];
always @(posedge clk) begin
$display("cyc = %d (%08x) x[1] = %0d, x[0] = %0d, y1 = %0d, y0 = %0d z[1] = %02x z[1] = %02x", cyc, cyc, x[1], x[0], y1, y0, z[1], z[0]);
`check(x[0], cyc[0]);
`check(x[1], cyc[0]);
`check(y0, cyc[0]);
`check(y1, cyc[0]);
`check(z[0], {8{cyc[0]}});
`check(z[1], {8{cyc[0]}});
`check(pair.a, cyc[0]);
`check(pair.b, cyc[0]);
x[1] <= ~x[1];
y1 <= ~y1;
for (int i = 0; i < 8; ++i) z[1][i] <= ~z[1][i];
pair.b <= ~pair.b;
cyc = cyc + 1;
sameAsCycButCantBeOptimized_0 = cyc;
sameAsCycButCantBeOptimized_1 = cyc;
sameAsCycButCantBeOptimized_2 = cyc;
if (cyc == 99) begin
$display(x);
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -1,11 +1,30 @@
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:18:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array' %Warning-MULTIDRIVEN: t/t_order_blkandnblk_bad.v:33:6: Variable also written to in always_comb (IEEE 1800-2023 9.2.2.2): 'unpacked'
: ... note: In instance 't'
t/t_order_blkandnblk_bad.v:33:6:
33 | unpacked.b <= unpacked.a;
| ^~~~~~~~
t/t_order_blkandnblk_bad.v:30:16: ... Location of always_comb write
30 | always_comb unpacked.a = i;
| ^~~~~~~~
... 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.
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:18:21: Unsupported: Blocking and non-blocking assignments to potentially overlapping bits of same packed variable: 't.array'
18 | logic [1:0][3:0] array; 18 | logic [1:0][3:0] array;
| ^~~~~ | ^~~~~
t/t_order_blkandnblk_bad.v:20:25: ... Location of blocking assignment t/t_order_blkandnblk_bad.v:20:25: ... Location of blocking assignment (bits [3:0])
20 | always_comb array[0] = i; 20 | always_comb array[0] = i;
| ^ | ^
t/t_order_blkandnblk_bad.v:23:15: ... Location of nonblocking assignment t/t_order_blkandnblk_bad.v:23:6: ... Location of nonblocking assignment (bits [3:0])
23 | array[1] <= array[0]; 23 | array[0] <= array[0];
| ^~ | ^~~~~
... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest ... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest
%Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:28:6: Unsupported: Blocking and non-blocking assignments to same non-packed variable: 't.unpacked'
28 | } unpacked;
| ^~~~~~~~
t/t_order_blkandnblk_bad.v:30:16: ... Location of blocking assignment
30 | always_comb unpacked.a = i;
| ^~~~~~~~
t/t_order_blkandnblk_bad.v:33:6: ... Location of nonblocking assignment
33 | unpacked.b <= unpacked.a;
| ^~~~~~~~
%Error: Exiting due to %Error: Exiting due to

View File

@ -20,8 +20,18 @@ module t (/*AUTOARG*/
always_comb array[0] = i; always_comb array[0] = i;
always @ (posedge clk) always @ (posedge clk)
array[1] <= array[0]; array[0] <= array[0];
assign o = array[idx]; struct {
logic [3:0] a;
logic [3:0] b;
} unpacked;
always_comb unpacked.a = i;
always @ (posedge clk)
unpacked.b <= unpacked.a;
assign o = array[idx] + unpacked.a;
endmodule endmodule

View File

@ -14,7 +14,6 @@ module t (/*AUTOARG*/
// verilator lint_off LATCH // verilator lint_off LATCH
// verilator lint_off UNOPT // verilator lint_off UNOPT
// verilator lint_off UNOPTFLAT // verilator lint_off UNOPTFLAT
// verilator lint_off BLKANDNBLK
// verilator lint_off MULTIDRIVEN // verilator lint_off MULTIDRIVEN
reg c1_start; initial c1_start = 0; reg c1_start; initial c1_start = 0;

View File

@ -10,7 +10,6 @@ module t (/*AUTOARG*/
); );
input clk; input clk;
// verilator lint_off BLKANDNBLK
// verilator lint_off COMBDLY // verilator lint_off COMBDLY
// verilator lint_off LATCH // verilator lint_off LATCH
// verilator lint_off UNOPT // verilator lint_off UNOPT

View File

@ -92,9 +92,7 @@ module Test
output logic [7:0] datao output logic [7:0] datao
); );
// verilator lint_off BLKANDNBLK
logic [7:0] datat; logic [7:0] datat;
// verilator lint_on BLKANDNBLK
for (genvar i = 0; i < 8; i++) begin for (genvar i = 0; i < 8; i++) begin
if (i%4 != 3) begin if (i%4 != 3) begin