Support procedural continuous assign/deassign (#7493)
This commit is contained in:
parent
8eca6b8fe7
commit
c69c11b2db
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
209
src/V3Force.cpp
209
src/V3Force.cpp
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ';'
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
For: 0
|
||||
array size: 2
|
||||
array size: 0
|
||||
user task
|
||||
do/while
|
||||
while
|
||||
repeat
|
||||
repeat
|
||||
forever
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue