Support procedural continuous assign/deassign (#7493)

This commit is contained in:
Artur Bieniek 2026-05-09 01:01:11 +02:00 committed by GitHub
parent 8eca6b8fe7
commit c69c11b2db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 640 additions and 107 deletions

View File

@ -956,6 +956,13 @@ List Of Warnings
port list, since these are references to interfaces/modports declared at a higher level and are
already specialized. These types of accesses do not require waiving HIERPARAM.
.. option:: IEEEMAYDEPRECATE
This feature is not yet deprecated, but may be in a future version of the IEEE standard.
This warning is to alert users that they may want to avoid using this feature, as it
may be removed in a future version of the IEEE standard, and thus may not be supported
in future versions of Verilator.
.. option:: IFDEPTH
Warns that if/if else statements have exceeded the depth specified with

View File

@ -539,6 +539,16 @@ public:
}
// but isPure() true
};
class AstDeassign final : public AstNodeStmt {
// Procedural 'deassign' statement
// @astgen op1 := lhsp : AstNodeExpr
public:
AstDeassign(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Deassign(fl) {
this->lhsp(lhsp);
}
ASTGEN_MEMBERS_AstDeassign;
};
class AstDelay final : public AstNodeStmt {
// Delay statement
// @astgen op1 := lhsp : AstNodeExpr // Delay value (or min for range)

View File

@ -113,6 +113,7 @@ public:
GENUNNAMED, // Generate unnamed, without label
HIERBLOCK, // Ignored hierarchical block setting
HIERPARAM, // Parameter using hierarchical value
IEEEMAYDEPRECATE, // Feature may be deprecated in future IEEE standard
IFDEPTH, // If statements too deep
IGNOREDRETURN, // Ignoring return value (function as task)
IMPERFECTSCH, // Imperfect schedule (disabled by default). Historical, never issued.
@ -225,20 +226,21 @@ public:
"CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN",
"CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI",
"FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH",
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH",
"LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN",
"MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT",
"PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
"PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
"SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",
"UNOPT", "UNOPTFLAT", "UNOPTTHREADS", "UNPACKED", "UNSATCONSTR", "UNSIGNED", "UNUSED",
"UNUSEDGENVAR", "UNUSEDLOOP", "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL",
"USERINFO", "USERWARN", "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT",
"WIDTHEXPAND", "WIDTHTRUNC", "WIDTHXZEXPAND", "ZERODLY", "ZEROREPL", " MAX"};
"FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM",
"IEEEMAYDEPRECATE", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT",
"IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY",
"INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP",
"MODMISSING", "MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD",
"NORETURN", "NULLPORT", "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING",
"PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PREPROCZERO", "PROCASSINIT",
"PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PROTOTYPEMIS", "RANDC", "REALCVT",
"REDEFMACRO", "RISEFALLDLY", "SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN",
"SPLITVAR", "STATICVAR", "STMTDLY", "SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET",
"TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS",
"UNPACKED", "UNSATCONSTR", "UNSIGNED", "UNUSED", "UNUSEDGENVAR", "UNUSEDLOOP",
"UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL", "USERINFO", "USERWARN",
"VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "WIDTHEXPAND", "WIDTHTRUNC",
"WIDTHXZEXPAND", "ZERODLY", "ZEROREPL", " MAX"};
return names[m_e];
}
// Warnings that default to off

View File

@ -138,9 +138,14 @@ private:
const VNUser2InUse m_user2InUse;
std::unordered_map<AstVar*, VarForceInfo> m_varInfo;
std::unordered_set<AstVar*> m_clockedWrites;
std::unordered_map<AstVar*, std::vector<ForceInfo*>> m_rhsDepToForces;
bool m_doingAssign = false; // If true, we're processing procedural continuous assign
// statements instead of force statements
public:
ForceState() = default;
ForceState(bool doingAssign)
: m_doingAssign{doingAssign} {}
VL_UNCOPYABLE(ForceState);
// STATIC METHODS
@ -197,6 +202,26 @@ public:
static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user1(); }
static void markNonReplaceable(AstVarRef* const nodep) { nodep->user1SetOnce(); }
static std::vector<ForceInfo*> forceInfosInIdOrder(VarForceInfo& info) {
std::vector<ForceInfo*> forceps;
forceps.reserve(info.m_forces.size());
for (auto& it : info.m_forces) forceps.push_back(&it.second);
std::sort(forceps.begin(), forceps.end(), [](const ForceInfo* ap, const ForceInfo* bp) {
return ap->m_forceId < bp->m_forceId;
});
return forceps;
}
static std::vector<const ForceInfo*> forceInfosInIdOrder(const VarForceInfo& info) {
std::vector<const ForceInfo*> forceps;
forceps.reserve(info.m_forces.size());
for (const auto& it : info.m_forces) forceps.push_back(&it.second);
std::sort(forceps.begin(), forceps.end(), [](const ForceInfo* ap, const ForceInfo* bp) {
return ap->m_forceId < bp->m_forceId;
});
return forceps;
}
static bool isOpaquePathSelector(const AstNode* nodep) {
return VN_IS(nodep, Sel) || VN_IS(nodep, NodeSel) || VN_IS(nodep, StructSel);
}
@ -270,6 +295,30 @@ public:
return info;
}
AstNodeExpr* addRhsValueReads(const VarForceInfo& varInfo, AstNodeExpr* exprp) const {
if (!doingAssign()) return exprp;
const std::vector<const ForceInfo*> forceps = forceInfosInIdOrder(varInfo);
if (forceps.empty()) return exprp;
// VlForceVec stores pointers to RHS shadows, so expose those reads to scheduling.
AstCExpr* const cexprp = new AstCExpr{exprp->fileline(), AstCExpr::Pure{}};
cexprp->dtypeFrom(exprp);
cexprp->add("(");
for (const ForceInfo* const finfop : forceps) {
UASSERT_OBJ(finfop->m_rhsVarVscp, exprp, "No RHS var for forced variable");
AstVarRef* const refp
= new AstVarRef{exprp->fileline(), finfop->m_rhsVarVscp, VAccess::READ};
markNonReplaceable(refp);
cexprp->add("(void)(");
cexprp->add(refp);
cexprp->add("), ");
}
cexprp->add(exprp);
cexprp->add(")");
return cexprp;
}
AstNodeExpr* createForceReadCall(const VarForceInfo& varInfo, FileLine* flp, VCMethod method,
AstNodeExpr* originalExprp, AstNode* dtypeFromp,
AstNodeExpr* indexExprp) const {
@ -277,7 +326,8 @@ public:
originalExprp->foreach(
[](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); });
AstNodeExpr* const origValp = castToNodeDType(originalExprp, dtypeFromp);
AstNodeExpr* const origValp
= addRhsValueReads(varInfo, castToNodeDType(originalExprp, dtypeFromp));
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, varInfo.m_forceVecVscp, VAccess::READ}, method, origValp};
@ -309,6 +359,11 @@ public:
VarForceInfo& getOrCreateVarInfo(AstVar* varp) { return m_varInfo[varp]; }
void markClockedWrite(AstVar* varp) { m_clockedWrites.insert(varp); }
bool hasClockedWrite(AstVar* varp) const { return m_clockedWrites.count(varp); }
bool doingAssign() const { return m_doingAssign; }
const VarForceInfo* getVarInfo(AstVar* varp) const {
const auto it = m_varInfo.find(varp);
return it != m_varInfo.end() ? &it->second : nullptr;
@ -331,8 +386,9 @@ public:
AstCDType* const forceVecDtypep = new AstCDType{flp, "VlForceVec"};
v3Global.rootp()->typeTablep()->addTypesp(forceVecDtypep);
AstVar* const forceVecVarp
= new AstVar{flp, VVarType::MEMBER, varp->name() + "__VforceVec", forceVecDtypep};
AstVar* const forceVecVarp = new AstVar{
flp, VVarType::MEMBER,
varp->name() + (m_doingAssign ? "_VassignVec" : "__VforceVec"), forceVecDtypep};
forceVecVarp->funcLocal(false);
forceVecVarp->isInternal(true);
varp->addNextHere(forceVecVarp);
@ -340,8 +396,22 @@ public:
scopep->addVarsp(info.m_forceVecVscp);
}
info.m_forces.emplace(forceStmtp, ForceInfo{rangeLsb, rangeMsb, padLsb, padMsb, forceId,
auto pair = info.m_forces.emplace(forceStmtp,
ForceInfo{rangeLsb, rangeMsb, padLsb, padMsb, forceId,
hasArraySel, nullptr, rhsExprp});
ForceInfo& finfo = pair.first->second;
if (doingAssign()) {
std::vector<AstVar*> depVarps;
finfo.m_rhsExprp->foreach([&](AstVarRef* const refp) {
if (!refp->access().isReadOnly()) return;
AstVar* const depVarp = refp->varp();
if (depVarp
&& std::find(depVarps.begin(), depVarps.end(), depVarp) == depVarps.end()) {
depVarps.push_back(depVarp);
}
});
for (AstVar* const depVarp : depVarps) m_rhsDepToForces[depVarp].push_back(&finfo);
}
UINFO(3, "Added force ID " << forceId << " for " << varp->name() << " [" << rangeMsb << ":"
<< rangeLsb << "]\n");
@ -413,14 +483,7 @@ public:
UASSERT_OBJ(scopep, varp, "Missing scope for force RHS vars");
FileLine* const flp = varp->fileline();
// Process force entries in stable force-id order.
std::vector<ForceInfo*> forceps;
forceps.reserve(info.m_forces.size());
for (auto& fit : info.m_forces) forceps.push_back(&fit.second);
std::sort(forceps.begin(), forceps.end(),
[](const ForceInfo* ap, const ForceInfo* bp) {
return ap->m_forceId < bp->m_forceId;
});
const std::vector<ForceInfo*> forceps = forceInfosInIdOrder(info);
for (ForceInfo* const finfop : forceps) {
ForceInfo& finfo = *finfop;
@ -429,7 +492,8 @@ public:
// Create per-force temporary storage for the captured RHS value.
AstVar* const rhsVarp
= new AstVar{flp, VVarType::VAR,
varp->name() + "__VforceRHS" + std::to_string(finfo.m_forceId),
varp->name() + (doingAssign() ? "_VassignRHS" : "__VforceRHS")
+ std::to_string(finfo.m_forceId),
finfo.m_rhsExprp->dtypep()};
rhsVarp->noSubst(true);
rhsVarp->sigPublic(true);
@ -514,6 +578,30 @@ public:
}
}
AstNode* createRhsUpdatesForWrite(FileLine* flp, AstVar* writtenVarp) const {
if (!doingAssign()) return nullptr;
const auto it = m_rhsDepToForces.find(writtenVarp);
if (it == m_rhsDepToForces.end()) return nullptr;
AstNode* headp = nullptr;
AstNode* tailp = nullptr;
for (const ForceInfo* const finfop : it->second) {
UASSERT_OBJ(finfop->m_rhsVarVscp, writtenVarp, "No RHS var for forced variable");
UASSERT_OBJ(finfop->m_rhsExprp, writtenVarp, "Missing RHS expression");
AstAssign* const updatep
= new AstAssign{flp, new AstVarRef{flp, finfop->m_rhsVarVscp, VAccess::WRITE},
finfop->m_rhsExprp->cloneTreePure(false)};
if (tailp) {
tailp->addNextHere(updatep);
} else {
headp = updatep;
}
tailp = updatep;
}
return headp;
}
const ForceInfo& getForceInfo(AstAssignForce* forceStmtp) const {
AstVar* varp = getOneVarRef(forceStmtp->lhsp())->varp();
auto it = m_varInfo.find(varp);
@ -551,11 +639,30 @@ public:
}
};
// Split deassign concat LHS before converting to release internals.
static void splitDeassign(AstDeassign* nodep) {
AstConcat* const concatp = VN_CAST(nodep->lhsp(), Concat);
if (!concatp) return;
FileLine* const flp = nodep->fileline();
AstDeassign* const newLp = new AstDeassign{flp, concatp->lhsp()->unlinkFrBack()};
AstDeassign* const newRp = new AstDeassign{flp, concatp->rhsp()->unlinkFrBack()};
AstNodeExpr* const conp = concatp->unlinkFrBack();
nodep->replaceWith(newLp);
newLp->addNextHere(newRp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(conp->deleteTree(), conp);
splitDeassign(newLp);
splitDeassign(newRp);
}
//######################################################################
// ForceDiscoveryVisitor - Discover force statements
class ForceDiscoveryVisitor final : public VNVisitorConst {
ForceState& m_state;
bool m_inClockedActive = false;
void visit(AstAssignForce* nodep) override {
if (nodep->user2()) return; // External force statements are pre-registered.
@ -599,6 +706,21 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
rangeInfo.m_padMsb, rangeInfo.m_hasArraySel);
}
void visit(AstAssign* nodep) override {
if (m_state.doingAssign() && m_inClockedActive) {
if (AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef)) {
m_state.markClockedWrite(lhsp->varp());
}
}
iterateChildrenConst(nodep);
}
void visit(AstActive* nodep) override {
VL_RESTORER(m_inClockedActive);
m_inClockedActive = nodep->hasClocked();
iterateChildrenConst(nodep);
}
void visit(AstVarScope* nodep) override {
if (nodep->varp()->isForceable()) {
if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) {
@ -870,7 +992,12 @@ class ForceConvertVisitor final : public VNVisitor {
// IEEE 1800-2023 10.6.2: When released, if the variable is not continuously driven,
// it maintains its current value until the next procedural assignment.
if (!releasedVarp->isContinuously()) {
const bool fullBitwiseRelease
= ForceState::isBitwiseDType(releasedVarp) && !rangeInfo.m_hasArraySel && !selp
&& rangeInfo.m_rangeLsb == 0 && rangeInfo.m_rangeMsb == releasedVarp->width() - 1;
if (!releasedVarp->isContinuously()
&& !(m_state.doingAssign() && m_state.hasClockedWrite(releasedVarp)
&& fullBitwiseRelease)) {
// Member/struct paths on non-bitwise types do not lower to a plain VarRef/bit range,
// so their current forced value is recovered via the same synthetic path index.
// if (!continuously_driven) lhs = force_read_current(lhs_path);
@ -936,7 +1063,20 @@ class ForceReplaceVisitor final : public VNVisitor {
m_stmtp = nodep;
iterate(nodep->lhsp());
iterate(nodep->rhsp());
if (AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(nodep->lhsp(), true), VarRef)) {
if (AstNode* const updatep
= m_state.createRhsUpdatesForWrite(nodep->fileline(), lhsp->varp())) {
nodep->addNextHere(updatep);
}
}
}
void visit(AstAssignCont* nodep) override {
VL_RESTORER(m_stmtp);
m_stmtp = nodep;
iterateAndNextNull(nodep->timingControlp());
iterate(nodep->rhsp());
}
void visit(AstDeassign*) override {}
void visit(AstCFunc* nodep) override { iterateLogic(nodep); }
void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); }
void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); }
@ -1074,10 +1214,47 @@ public:
void V3Force::forceAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":\n");
if (!v3Global.hasForceableSignals()) return;
ForceState state;
ForceState state{false};
{ ForceDiscoveryVisitor{nodep, state}; }
state.finalizeRhsVars();
{ ForceConvertVisitor{nodep, state}; }
{ ForceReplaceVisitor{nodep, state}; }
V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3);
}
void V3Force::assignAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":\n");
if (!v3Global.hasAssignDeassign()) return;
std::vector<AstDeassign*> deassignps;
nodep->foreach([&](AstDeassign* deassignp) { deassignps.push_back(deassignp); });
for (AstDeassign* const deassignp : deassignps) splitDeassign(deassignp);
std::vector<AstAssignCont*> assignContps;
deassignps.clear();
nodep->foreach([&](AstNodeStmt* nodep) {
if (AstAssignCont* const assignContp = VN_CAST(nodep, AssignCont)) {
assignContps.push_back(assignContp);
} else if (AstDeassign* const deassignp = VN_CAST(nodep, Deassign)) {
deassignps.push_back(deassignp);
}
});
for (AstAssignCont* const assignp : assignContps) {
assignp->replaceWith(new AstAssignForce{assignp->fileline(),
assignp->lhsp()->unlinkFrBack(),
assignp->rhsp()->unlinkFrBack()});
assignp->deleteTree();
}
for (AstDeassign* const deassignp : deassignps) {
deassignp->replaceWith(
new AstRelease{deassignp->fileline(), deassignp->lhsp()->cloneTreePure(true)});
deassignp->deleteTree();
}
ForceState state{true};
{ ForceDiscoveryVisitor{nodep, state}; }
state.finalizeRhsVars();
{ ForceConvertVisitor{nodep, state}; }
{ ForceReplaceVisitor{nodep, state}; }
V3Global::dumpCheckGlobalTree("assign-deassign", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -28,6 +28,7 @@ class AstNetlist;
class V3Force final {
public:
static void forceAll(AstNetlist* nodep) VL_MT_DISABLED;
static void assignAll(AstNetlist* nodep) VL_MT_DISABLED;
};
#endif // Guard

View File

@ -128,6 +128,7 @@ class V3Global final {
bool m_usesForce = false; // Design uses force/release statements
bool m_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay)
bool m_hasForceableSignals = false; // Need to apply V3Force pass
bool m_hasAssignDeassign = false; // Need to apply V3Force pass for assign/deassign statements
bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted
bool m_useParallelBuild = false; // Use parallel build for model
bool m_useRandSequence = false; // Has `randsequence`
@ -206,6 +207,8 @@ public:
void setUsesZeroDelay() { m_usesZeroDelay = true; }
bool hasForceableSignals() const { return m_hasForceableSignals; }
void setHasForceableSignals() { m_hasForceableSignals = true; }
bool hasAssignDeassign() const { return m_hasAssignDeassign; }
void setHasAssignDeassign() { m_hasAssignDeassign = true; }
bool usesForce() const { return m_usesForce; }
void setUsesForce() { m_usesForce = true; }
bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; }

View File

@ -241,6 +241,7 @@ class LinkJumpVisitor final : public VNVisitor {
new AstClassRefDType{fl, v3Global.rootp()->stdPackageClassp(), nullptr}, nullptr}};
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
processQueuep->processQueue(true);
processQueuep->setIgnoreSchedWrite();
topPkgp->addStmtsp(processQueuep);
return processQueuep;
}

View File

@ -169,6 +169,13 @@ class LinkLValueVisitor final : public VNVisitor {
m_setForcedByCode = true;
iterateAndNextNull(nodep->lhsp());
}
void visit(AstDeassign* nodep) override {
VL_RESTORER(m_setRefLvalue);
VL_RESTORER(m_setContinuously);
m_setRefLvalue = VAccess::WRITE;
m_setContinuously = false;
iterateAndNextNull(nodep->lhsp());
}
void visit(AstFireEvent* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;

View File

@ -105,6 +105,7 @@ class OrderGraphBuilder final : public VNVisitor {
bool m_inPre = false; // Underneath AlwaysPre
bool m_inPost = false; // Underneath AstAlwaysPost
std::function<bool(const AstVarScope*)> m_readTriggersCombLogic;
V3Sched::util::VarScopeSet m_forceReadEdgeIgnores;
// METHODS
@ -112,12 +113,16 @@ class OrderGraphBuilder final : public VNVisitor {
UASSERT_OBJ(!m_logicVxp, nodep, "Should not nest");
// Reset VarUsage
AstNode::user2ClearTree();
m_forceReadEdgeIgnores.clear();
if (!m_inClocked)
V3Sched::util::collectForceReadEdgeIgnores(nodep, m_forceReadEdgeIgnores);
// Create LogicVertex for this logic node
m_logicVxp = new OrderLogicVertex{m_graphp, m_scopep, m_domainp, m_hybridp, nodep};
// Gather variable dependencies based on usage
iterateChildren(nodep);
// Finished with this logic
m_logicVxp = nullptr;
m_forceReadEdgeIgnores.clear();
}
OrderVarVertex* getVarVertex(AstVarScope* varscp, VarVertexType type) {
@ -207,6 +212,7 @@ class OrderGraphBuilder final : public VNVisitor {
// latch?).
con = false;
}
if (!m_inClocked && m_forceReadEdgeIgnores.count(varscp)) con = false;
}
// Note: See V3OrderGraph.h about the roles of the various vertex types

View File

@ -24,6 +24,7 @@
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@ -33,6 +34,49 @@ class SenExprBuilder;
namespace V3Sched {
namespace util {
using VarScopeSet = std::unordered_set<const AstVarScope*>;
inline bool isVlForceVec(const AstVarScope* vscp) {
const AstCDType* const dtypep = VN_CAST(vscp->dtypep()->skipRefp(), CDType);
return dtypep && dtypep->name() == "VlForceVec";
}
inline bool isForceReadMethod(VCMethod method) {
return method == VCMethod::FORCE_READ || method == VCMethod::FORCE_READ_INDEX;
}
inline void collectForceReadEdgeIgnores(AstNode* nodep, VarScopeSet& out) {
VarScopeSet writtenForceVecs;
VarScopeSet writtenVars;
nodep->foreach([&](const AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
if (!refp->access().isWriteOrRW()) return;
if (!refp->varp()->ignoreSchedWrite()) writtenVars.emplace(vscp);
if (!isVlForceVec(vscp)) return;
writtenForceVecs.emplace(vscp);
if (!refp->varp()->ignoreSchedWrite()) out.emplace(vscp);
});
if (writtenForceVecs.empty() || writtenVars.empty()) return;
nodep->foreach([&](const AstCMethodHard* callp) {
if (!isForceReadMethod(callp->method())) return;
const AstVarRef* const fromRefp = VN_CAST(callp->fromp(), VarRef);
if (!fromRefp || !writtenForceVecs.count(fromRefp->varScopep())) return;
AstNodeExpr* const origp = callp->pinsp();
if (!origp) return;
origp->foreach([&](const AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
if (refp->access().isReadOrRW() && writtenVars.count(vscp)) out.emplace(vscp);
});
});
}
} // namespace util
//============================================================================
// Throughout scheduling, we need to keep hold of AstActive nodes, together with the AstScope that
// they are under. LogicByScope is simply a vector of such pairs, with some additional convenience

View File

@ -152,6 +152,9 @@ std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
V3Sched::util::VarScopeSet forceReadEdgeIgnores;
V3Sched::util::collectForceReadEdgeIgnores(nodep, forceReadEdgeIgnores);
nodep->foreach([&](AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
SchedAcyclicVarVertex* const vvtxp = getVarVertex(vscp);
@ -164,7 +167,8 @@ std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
// If read, add var -> logic edge
// Note: Use same heuristic as ordering does to ignore written variables
// TODO: Use live variable analysis.
if (refp->access().isReadOrRW() && !vscp->user3SetOnce() && !vscp->user2())
if (refp->access().isReadOrRW() && !vscp->user3SetOnce() && !vscp->user2()
&& !forceReadEdgeIgnores.count(vscp))
addEdge(vvtxp, lvtxp, weight, true);
});
}

View File

@ -197,9 +197,12 @@ class SchedGraphBuilder final : public VNVisitor {
}
// Add edges based on references
nodep->foreach([this, logicVtxp](const AstVarRef* vrefp) {
V3Sched::util::VarScopeSet forceReadEdgeIgnores;
V3Sched::util::collectForceReadEdgeIgnores(nodep, forceReadEdgeIgnores);
nodep->foreach([this, logicVtxp, &forceReadEdgeIgnores](const AstVarRef* vrefp) {
AstVarScope* const vscp = vrefp->varScopep();
if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)) {
if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)
&& !forceReadEdgeIgnores.count(vscp)) {
new V3GraphEdge{m_graphp, getVarVertex(vscp), logicVtxp, 10};
}
if (vrefp->access().isWriteOrRW() && !vrefp->varp()->ignoreSchedWrite()) {

View File

@ -217,15 +217,15 @@ std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
V3Sched::util::VarScopeSet forceReadEdgeIgnores;
V3Sched::util::collectForceReadEdgeIgnores(nodep, forceReadEdgeIgnores);
nodep->foreach([&](AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
SchedReplicateVarVertex* const vvtxp = getVarVertex(vscp);
// If read, add var -> logic edge
// Note: Use same heuristic as ordering does to ignore written variables
// TODO: Use live variable analysis.
if (refp->access().isReadOrRW() && !vscp->user3SetOnce()
&& readTriggersThisLogic(vscp) && !vscp->user2()) { //
&& readTriggersThisLogic(vscp) && !vscp->user2()
&& !forceReadEdgeIgnores.count(vscp)) { //
addEdge(vvtxp, lvtxp);
}
// If written, add logic -> var edge

View File

@ -6306,6 +6306,12 @@ class WidthVisitor final : public VNVisitor {
UASSERT_OBJ(nodep->lhsp()->dtypep()->widthSized(), nodep, "How can LValue be unsized?");
checkForceReleaseLhs(nodep, nodep->lhsp());
}
void visit(AstDeassign* nodep) override {
userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p());
UASSERT_OBJ(nodep->lhsp()->dtypep(), nodep, "L-value is untyped");
UASSERT_OBJ(nodep->lhsp()->dtypep()->widthSized(), nodep, "L-value width is unsized");
checkForceReleaseLhs(nodep, nodep->lhsp());
}
static bool isFormatNonNumericArg(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();

View File

@ -427,11 +427,6 @@ private:
iterateAndNextNull(nodep->rhsp());
}
editDType(nodep);
AstNode* const controlp
= nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr;
nodep->replaceWith(new AstAssign{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack(), controlp});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssignDly* nodep) override {
iterateAndNextNull(nodep->timingControlp());

View File

@ -425,6 +425,10 @@ static void process() {
// forcing.
V3Force::forceAll(v3Global.rootp());
// Convert assign/deassign statements to forces on generated variables, so they can be
// handled by the same logic as regular force/release statements.
V3Force::assignAll(v3Global.rootp());
// DFG optimization
if (v3Global.opt.fDfg()) V3DfgOptimizer::optimize(v3Global.rootp());

View File

@ -3595,10 +3595,12 @@ statement_item<nodeStmtp>: // IEEE: statement_item
| fexprLvalue yP_LTE cycle_delay expr ';'
{ $$ = new AstAssignDly{$2, $1, $4, $3}; }
//UNSUP cycle_delay fexprLvalue yP_LTE ';' { UNSUP }
| yASSIGN idClassSel '=' delay_or_event_controlE expr ';'
{ $$ = new AstAssignCont{$1, $2, $5, $4}; }
| yASSIGN variable_lvalue '=' delay_or_event_controlE expr ';'
{ $$ = new AstAssignCont{$1, $2, $5, $4};
$1->v3warn(IEEEMAYDEPRECATE, "Feature may be deprecated in future IEEE standard"); v3Global.setHasAssignDeassign(); }
| yDEASSIGN variable_lvalue ';'
{ $$ = nullptr; BBUNSUP($1, "Unsupported: Verilog 1995 deassign"); DEL($2); }
{ $$ = new AstDeassign{$1, $2};
$1->v3warn(IEEEMAYDEPRECATE, "Feature may be deprecated in future IEEE standard"); v3Global.setHasAssignDeassign(); }
| yFORCE variable_lvalue '=' expr ';'
{ $$ = new AstAssignForce{$1, $2, $4}; v3Global.setHasForceableSignals(); }
| yRELEASE variable_lvalue ';'

View File

@ -1,3 +1,8 @@
%Warning-IEEEMAYDEPRECATE: t/t_assign_cont_automatic_bad.v:14:7: Feature may be deprecated in future IEEE standard
14 | assign g = signed'(l);
| ^~~~~~
... For warning description see https://verilator.org/warn/IEEEMAYDEPRECATE?v=latest
... Use "/* verilator lint_off IEEEMAYDEPRECATE */" and lint_on around source to disable this message.
%Error: t/t_assign_cont_automatic_bad.v:14:26: Automatic lifetime variable not allowed in continuous assignment (IEEE 1800-2023 6.21): 'l'
: ... note: In instance 't'
14 | assign g = signed'(l);

View File

@ -4,14 +4,15 @@
# 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: 2024 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.top_filename = "t/t_lint_unsup_deassign.v"
test.scenarios('simulator')
test.lint(fails=True, expect_filename=test.golden_filename)
test.compile(verilator_flags2=["--binary --timing", "-Wno-IEEEMAYDEPRECATE"])
test.execute()
test.passes()

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
module t;
reg a;
reg b;
reg c;
reg d;
reg control;
reg clock = 0;
always @(posedge clock) {a, b, c, d} = 4'h3;
always @(control)
if (control)
assign {a, b, c, d} = 4'h2;
else
deassign {a, b, c, d};
always begin
#2;
clock = ~clock;
end
initial begin
#3;
`checkh({a, b, c, d}, 4'h3)
#2;
control = 1;
#1;
`checkh({a, b, c, d}, 4'h2)
#3;
control = 0;
#2;
`checkh({a, b, c, d}, 4'h3)
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -4,13 +4,15 @@
# 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: 2024 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.lint(verilator_flags2=["--bbox-unsup"])
test.compile(verilator_flags2=["-Wno-IEEEMAYDEPRECATE"])
test.execute()
test.passes()

View File

@ -0,0 +1,83 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
integer cyc; initial cyc = 0;
wire q;
logic d=0, clear, preset;
dff flipflop(q, d, clear, preset, clk);
//clear and preset signals are in inverted logic
always @ (posedge clk) begin
cyc <= cyc + 1;
if(cyc==0) begin
clear=1;
preset=0;
d=0;
end
else if(cyc==1) begin
`checkh(q, 1);
preset=1;
d=1;
end
else if(cyc==2) begin
`checkh(q, 1);
clear=0;
end
else if(cyc==3) begin
`checkh(q, 0);
preset=0;
end
else if(cyc==4) begin
`checkh(q, 0);
clear=1;
preset=1;
end
else if(cyc==5) begin
`checkh(q, 1);
d=0;
end
else if(cyc==6) begin
`checkh(q, 0);
d=1;
end
else if(cyc==7) begin
`checkh(q, 1);
end
else if (cyc==8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule
// From IEEE 1800-2023 10.6.1
module dff (q, d, clear, preset, clock);
output q;
input d, clear, preset, clock;
logic q;
always @(clear or preset)
if (!clear)
assign q = 0;
else if (!preset)
assign q = 1;
else
deassign q;
always @(posedge clock)
q = d;
endmodule

View File

@ -4,14 +4,14 @@
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(timing_loop=True, verilator_flags2=["--timing", "-Wno-IEEEMAYDEPRECATE"])
test.execute()

View File

@ -1,24 +1,45 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Antmicro
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkb(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='b%x exp='b%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
module t;
reg [2:0] a = 0;
reg [1:0] a=0, b=1;
reg [1:0] r;
initial begin
a = 1;
if (a != 1) $stop;
force a = 2;
if (a != 2) $stop;
a = 3;
if (a != 2) $stop;
$write("*-* All Finished *-*\n");
r = 2'b00;
assign r = 2'b01;
`checkb(r, 2'b01)
r = 2'b00; // ignored
#1; `checkb(r, 2'b01)
deassign r;
`checkb(r, 2'b01)
r = 2'b00;
`checkb(r, 2'b00)
assign r = a;
`checkb(r, 2'b00)
a = 2'b01;
`checkb(r, 2'b01)
a = 2'b00;
`checkb(r, 2'b00)
force r = a + b;
a = 2'b00; b = 2'b00;
#1; `checkb(r, 2'b00)
a = 2'b01; b = 2'b01;
#1; `checkb(r, 2'b10)
assign r = b; // covered
r = 2'b11; // ignored
`checkb(r, 2'b10)
release r;
`checkb(r, 2'b01)
b = 2'b00;
`checkb(r, 2'b00)
$finish;
end
endmodule

View File

@ -0,0 +1,9 @@
For: 0
array size: 2
array size: 0
user task
do/while
while
repeat
repeat
forever

View File

@ -0,0 +1,18 @@
#!/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('simulator')
test.compile(timing_loop=True, verilator_flags2=["--timing", "-Wno-IEEEMAYDEPRECATE"])
test.execute(expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,103 @@
// DESCRIPTION: Verilator: force/release and assign/deassign in combinational logic
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilator lint_off ALWCOMBORDER
// verilator lint_off LATCH
// verilator lint_off MULTIDRIVEN
// verilator lint_off UNDRIVEN
// verilator lint_off UNUSEDSIGNAL
// verilator lint_off COMBDLY
// verilator lint_off WIDTHEXPAND
// verilator lint_off WIDTHTRUNC
module t (
input logic src,
output logic assign_out,
output logic comb_out,
output logic latch_out
);
logic assign_sig;
logic comb_sig;
logic latch_sig;
reg a;
reg q, d;
event foo;
real rl;
int ar [];
int start = 0;
int stop = 1;
int step = 1;
int done = 0;
task a_task;
real trl;
event tevt;
reg tvr;
$display("user task");
endtask
always_comb begin : comb_force
comb_out = comb_sig;
force comb_sig = src;
release comb_sig;
end
always_latch begin : latch_force
if (src) latch_out = latch_sig;
force latch_sig = src;
release latch_sig;
end
always_comb begin : comb_assign
assign_out = assign_sig;
assign assign_sig = src;
deassign assign_sig;
end
always_comb begin: blk_name
event int1, int2;
real intrl;
q <= d;
-> foo;
rl = 0.0;
rl <= 1.0;
ar = new [2];
for (int idx = start; idx < stop; idx += step) $display("For: %0d", idx);
for (int idx = 0; done; idx = done + 1) $stop;
for (int idx = 0; idx; done = done + 1) $stop;
for (int idx = 0; idx; {done, idx} = done + 1) $stop;
for (int idx = 0; idx; idx <<= 1) $stop;
for (int idx = 0; idx; idx = idx << 1) $stop;
$display("array size: %0d", ar.size());
ar.delete();
$display("array size: %0d", ar.size());
a_task;
assign a = 1'b0;
deassign a;
do $display("do/while");
while (a);
force a = 1'b1;
release a;
while(a) begin
$display("while");
a = 1'b0;
end
repeat(2) $display("repeat");
disable out_name;
forever begin
$display("forever");
disable blk_name; // This one should not generate a warning
end
end
initial begin: out_name
#2 $stop;
end
initial #10 $finish;
endmodule

View File

@ -1,3 +1,8 @@
%Warning-IEEEMAYDEPRECATE: t/t_force_input_assign_bad.v:27:5: Feature may be deprecated in future IEEE standard
27 | assign s3.i = 2;
| ^~~~~~
... For warning description see https://verilator.org/warn/IEEEMAYDEPRECATE?v=latest
... Use "/* verilator lint_off IEEEMAYDEPRECATE */" and lint_on around source to disable this message.
%Error-ASSIGNIN: t/t_force_input_assign_bad.v:20:8: Assigning to input/const variable: 'i'
: ... note: In instance 't'
20 | s1.i = 2;

View File

@ -1,4 +0,0 @@
0 d=0,e=0
10 d=1,e=1
20 d=1,e=0
%Error: t/t_force_release.v:39: got='h1 exp='h00000000

View File

@ -9,10 +9,10 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute(expect_filename=test.golden_filename)
test.execute()
test.passes()

View File

@ -18,7 +18,7 @@ module t;
initial begin
$monitor("%d d=%b,e=%b", $stime, d, e);
assign d = a & b & c;
d = a & b & c;
a = 1;
b = 0;
c = 1;
@ -30,11 +30,6 @@ module t;
#10;
release d;
release e;
// TODO support procedural continuous assignments.
//
// As per IEEE 1800-2023 10.6.2, value of `d` should be updated
// after release. However, Verilator treats `assign` inside an initial block
// as procedural assign thus value update is not properly restored.
#10;
`checkh(d, 0);
`checkh(e, 0);

View File

@ -23,6 +23,6 @@ module t #(
input [B-1:0] b;
output logic [min(A,B)-1:0] c;
always_comb for (int i = 0; i < min(A, B); i++) assign c[i] = a[i] | b[i];
always_comb for (int i = 0; i < min(A, B); i++) c[i] = a[i] | b[i];
endmodule

View File

@ -13,7 +13,7 @@ module t (
`include "t_initial_inc.vh"
// surefire lint_off STMINI
initial assign user_loaded_value = 1;
initial user_loaded_value = 1;
initial _ranit = 0;

View File

@ -1,19 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2016 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input wire rst
);
integer q;
// verilator lint_off LATCH
always @(*)
if (rst) assign q = 0;
else deassign q;
// verilator lint_on LATCH
endmodule

View File

@ -1,5 +0,0 @@
%Error-UNSUPPORTED: t/t_lint_unsup_deassign.v:16:10: Unsupported: Verilog 1995 deassign
16 | else deassign q;
| ^~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to