diff --git a/include/verilated_types.h b/include/verilated_types.h index 9eae185d5..876a78511 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -81,11 +81,16 @@ constexpr IData VL_CLOG2_CE_Q(QData lhs) VL_PURE { return lhs <= 1 ? 0 : VL_CLOG2_CE_Q((lhs + 1) >> 1ULL) + 1; } -//=================================================================== -// VlProcess stores metadata of running processes +// Metadata of processes +class VlProcess; + +using VlProcessRef = std::shared_ptr; + class VlProcess final { // MEMBERS int m_state; // Current state of the process + VlProcessRef m_parentp = nullptr; // Parent process, if exists + std::set m_children; // Active child processes public: // TYPES @@ -98,16 +103,34 @@ public: }; // CONSTRUCTORS + // Construct independent process VlProcess() : m_state{RUNNING} {} + // Construct child process of parent + VlProcess(VlProcessRef parentp) + : m_state{RUNNING} + , m_parentp{parentp} { + m_parentp->attach(this); + } + + ~VlProcess() { + if (m_parentp) m_parentp->detach(this); + } + + void attach(VlProcess* childp) { m_children.insert(childp); } + void detach(VlProcess* childp) { m_children.erase(childp); } - // METHODS int state() { return m_state; } void state(int s) { m_state = s; } + void disable() { + state(KILLED); + disable_fork(); + } + void disable_fork() { + for (VlProcess* childp : m_children) childp->disable(); + } }; -using VlProcessRef = std::shared_ptr; - inline std::string VL_TO_STRING(const VlProcessRef& p) { return std::string("process"); } //=================================================================== diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 91c015958..2fea7ff71 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -86,7 +86,7 @@ private: bool m_recursive : 1; // Recursive or part of recursion bool m_underGenerate : 1; // Under generate (for warning) bool m_virtual : 1; // Virtual method in class - bool m_needProcess : 1; // Implements part of a process that allocates std::process + bool m_needProcess : 1; // Needs access to VlProcess of the caller VLifetime m_lifetime; // Lifetime VIsCached m_purity; // Pure state @@ -300,7 +300,7 @@ class AstNodeProcedure VL_NOT_FINAL : public AstNode { // IEEE procedure: initial, final, always // @astgen op2 := stmtsp : List[AstNode] // Note: op1 is used in some sub-types only bool m_suspendable : 1; // Is suspendable by a Delay, EventControl, etc. - bool m_needProcess : 1; // Implements part of a process that allocates std::process + bool m_needProcess : 1; // Uses VlProcess protected: AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp) : AstNode{t, fl} { @@ -608,7 +608,7 @@ private: bool m_dpiImportPrototype : 1; // This is the DPI import prototype (i.e.: provided by user) bool m_dpiImportWrapper : 1; // Wrapper for invoking DPI import prototype from generated code bool m_dpiTraceInit : 1; // DPI trace_init - bool m_needProcess : 1; // Implements part of a process that allocates std::process + bool m_needProcess : 1; // Needs access to VlProcess of the caller public: AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType = "") : ASTGEN_SUPER_CFunc(fl) { @@ -2096,19 +2096,23 @@ class AstBegin final : public AstNodeBlock { // Parents: statement // @astgen op1 := genforp : Optional[AstNode] - bool m_generate; // Underneath a generate - const bool m_implied; // Not inserted by user + bool m_generate : 1; // Underneath a generate + bool m_needProcess : 1; // Uses VlProcess + const bool m_implied : 1; // Not inserted by user public: // Node that puts name into the output stream AstBegin(FileLine* fl, const string& name, AstNode* stmtsp, bool generate = false, bool implied = false) : ASTGEN_SUPER_Begin(fl, name, stmtsp) , m_generate{generate} + , m_needProcess{false} , m_implied{implied} {} ASTGEN_MEMBERS_AstBegin; void dump(std::ostream& str) const override; void generate(bool flag) { m_generate = flag; } bool generate() const { return m_generate; } + void setNeedProcess() { m_needProcess = true; } + bool needProcess() const { return m_needProcess; } bool implied() const { return m_implied; } }; class AstFork final : public AstNodeBlock { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index c06e60907..4720c4b01 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2321,6 +2321,7 @@ void AstBegin::dump(std::ostream& str) const { if (generate()) str << " [GEN]"; if (genforp()) str << " [GENFOR]"; if (implied()) str << " [IMPLIED]"; + if (needProcess()) str << " [NPRC]"; } void AstCoverDecl::dump(std::ostream& str) const { this->AstNodeStmt::dump(str); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 0264059a3..deb4ceb61 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -410,7 +410,8 @@ void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const stri displayEmit(nodep, isScan); } -void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer) { +void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer, + bool inProcess) { puts("("); bool comma = false; if (nodep->funcp()->isLoose() && !nodep->funcp()->isStatic()) { @@ -422,6 +423,8 @@ void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPoint if (comma) puts(", "); if (VN_IS(nodep->backp(), CAwait) || !nodep->funcp()->isCoroutine()) { puts("vlProcess"); + } else if (inProcess) { + puts("std::make_shared(vlProcess)"); } else { puts("std::make_shared()"); } diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index f9b3bd3d6..871ec3624 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -205,7 +205,7 @@ public: } void emitOpName(AstNode* nodep, const string& format, AstNode* lhsp, AstNode* rhsp, AstNode* thsp); - void emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer); + void emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer, bool inProcess); void emitDereference(const string& pointer); void emitCvtPackStr(AstNode* nodep); void emitCvtWideArray(AstNode* nodep, AstNode* fromp); @@ -500,7 +500,7 @@ public: } puts(funcp->nameProtect()); } - emitCCallArgs(nodep, nodep->selfPointerProtect(m_useSelfForThis)); + emitCCallArgs(nodep, nodep->selfPointerProtect(m_useSelfForThis), m_cfuncp->needProcess()); } void visit(AstCMethodCall* nodep) override { const AstCFunc* const funcp = nodep->funcp(); @@ -508,7 +508,7 @@ public: iterateConst(nodep->fromp()); putbs("->"); puts(funcp->nameProtect()); - emitCCallArgs(nodep, ""); + emitCCallArgs(nodep, "", m_cfuncp->needProcess()); } void visit(AstCAwait* nodep) override { puts("co_await "); @@ -613,6 +613,7 @@ public: puts("]);\n"); } } + void visit(AstDisableFork* nodep) override { puts("vlProcess->disable_fork();\n"); } void visit(AstCReturn* nodep) override { puts("return ("); iterateAndNextConstNull(nodep->lhsp()); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 68807a097..d0aa33e64 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -330,7 +330,7 @@ private: AstJumpLabel* const labelp = findAddLabel(beginp, false); nodep->addNextHere(new AstJumpGo{nodep->fileline(), labelp}); } else { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: disable fork"); + nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork by name"); } nodep->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(nodep), nodep); diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index c3ffa9ef6..e072814e7 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -279,7 +279,6 @@ void transformForks(AstNetlist* const netlistp) { // STATE bool m_inClass = false; // Are we in a class? bool m_beginHasAwaits = false; // Does the current begin have awaits? - bool m_beginNeedProcess = false; // Does the current begin have process::self dependency? AstFork* m_forkp = nullptr; // Current fork AstCFunc* m_funcp = nullptr; // Current function @@ -358,9 +357,8 @@ void transformForks(AstNetlist* const netlistp) { UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork"); // Start with children, so later we only find awaits that are actually in this begin m_beginHasAwaits = false; - m_beginNeedProcess = false; iterateChildrenConst(nodep); - if (m_beginHasAwaits || m_beginNeedProcess) { + if (m_beginHasAwaits || nodep->needProcess()) { UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name"); // Create a function to put this begin's statements in FileLine* const flp = nodep->fileline(); @@ -384,7 +382,7 @@ void transformForks(AstNetlist* const netlistp) { } // Put the begin's statements in the function, delete the begin newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); - if (m_beginNeedProcess) { + if (nodep->needProcess()) { newfuncp->setNeedProcess(); newfuncp->addStmtsp(new AstCStmt{nodep->fileline(), "vlProcess->state(VlProcess::FINISHED);\n"}); @@ -405,10 +403,6 @@ void transformForks(AstNetlist* const netlistp) { m_beginHasAwaits = true; iterateChildrenConst(nodep); } - void visit(AstCCall* nodep) override { - if (nodep->funcp()->needProcess()) m_beginNeedProcess = true; - iterateChildrenConst(nodep); - } void visit(AstExprStmt* nodep) override { iterateChildren(nodep); } //-------------------- diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 6a10731b6..d2d6bae96 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -12,11 +12,27 @@ // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // -// TimingSuspendableVisitor locates all C++ functions and processes that contain timing controls, -// and marks them as suspendable. If a process calls a suspendable function, then it is also marked -// as suspendable. If a function calls or overrides a suspendable function, it is also marked as -// suspendable. TimingSuspendableVisitor creates a dependency graph to propagate this property. It -// does not perform any AST transformations. +// TimingSuspendableVisitor does not perform any AST transformations. +// Instead it propagates two types of flags: +// - flag "suspendable": (for detecting what will need to become a coroutine) +// The visitor locates all C++ functions and processes that contain timing controls, +// and marks them as suspendable. If a process calls a suspendable function, +// then it is also marked as suspendable. If a function calls or overrides +// a suspendable function, it is also marked as suspendable. +// TimingSuspendableVisitor creates a dependency graph to propagate this property. +// - flag "needs process": (for detecting what needs a VlProcess argument in signature) +// The visitor distinguishes 4 types of nodes: +// - T_ALLOCS_PROC: nodes that can allocate VlProcess (forks, always, initial etc.), +// - T_FORCES_PROC: nodes that make it necessary for the previous type to allocate VlProcess +// (like process::self which then wraps it, allowing use inside Verilog). +// - T_NEEDS_PROC: nodes that should obtain VlProcess if it will be allocated +// (all of the previous type + timing controls, so they could update process state), +// - T_HAS_PROC: nodes that are going to be emitted with a VlProcess argument. +// T_FORCES_PROC and T_NEEDS_PROC are propagated upwards up to the nodes of type T_ALLOCS_PROC, +// this is to detect which processes have to allocate VlProcess. Then nodes of these processes +// get marked as T_HAS_PROC and the flag is propagated downwards through nodes +// type T_NEEDS_PROC. Using only nodes type T_NEEDS_PROC assures the flags are only propagated +// through paths leading to nodes that actually use VlProcess. // // TimingControlVisitor is the one that actually performs transformations: // - for each intra-assignment timing control: @@ -70,8 +86,10 @@ VL_DEFINE_DEBUG_FUNCTIONS; enum NodeFlag : uint8_t { T_SUSPENDEE = 1 << 0, // Suspendable (due to dependence on another suspendable) T_SUSPENDER = 1 << 1, // Suspendable (has timing control) - T_HAS_PROC = 1 << 2, // Has an associated std::process - T_CALLS_PROC_SELF = 1 << 3, // Calls std::process::self + T_ALLOCS_PROC = 1 << 2, // Can allocate VlProcess + T_FORCES_PROC = 1 << 3, // Forces VlProcess allocation + T_NEEDS_PROC = 1 << 4, // Needs access to VlProcess if it's allocated + T_HAS_PROC = 1 << 5, // Has VlProcess argument in the signature }; enum ForkType : uint8_t { @@ -86,6 +104,11 @@ enum PropagationType : uint8_t { P_SIGNATURE = 3, // Propagation required to maintain C++ function's signature requirements }; +// Add timing flag to a node +static void addFlags(AstNode* const nodep, uint8_t flags) { nodep->user2(nodep->user2() | flags); } +// Check if a node has ALL of the expected flags set +static bool hasFlags(AstNode* const nodep, uint8_t flags) { return !(~nodep->user2() & flags); } + // ###################################################################### // Detect nodes affected by timing and/or requiring a process @@ -126,8 +149,8 @@ private: class SuspendDepVtx final : public DepVtx { VL_RTTI_IMPL(SuspendDepVtx, DepVtx) string dotColor() const override { - if (nodep()->user2() & T_SUSPENDER) return "red"; - if (nodep()->user2() & T_SUSPENDEE) return "blue"; + if (hasFlags(nodep(), T_SUSPENDER)) return "red"; + if (hasFlags(nodep(), T_SUSPENDEE)) return "blue"; return "black"; } @@ -140,8 +163,9 @@ private: class NeedsProcDepVtx final : public DepVtx { VL_RTTI_IMPL(NeedsProcDepVtx, DepVtx) string dotColor() const override { - if (nodep()->user2() & T_CALLS_PROC_SELF) return "red"; - if (nodep()->user2() & T_HAS_PROC) return "blue"; + if (hasFlags(nodep(), T_HAS_PROC)) return "blue"; + if (hasFlags(nodep(), T_NEEDS_PROC)) return "green"; + if (hasFlags(nodep(), T_FORCES_PROC)) return "red"; return "black"; } @@ -198,10 +222,10 @@ private: if (!nodep->user5p()) nodep->user5p(new NeedsProcDepVtx{&m_procGraph, nodep, classp}); return nodep->user5u().to(); } - // Set timing flag of a node + // Pass timing flag between nodes bool passFlag(const AstNode* from, AstNode* to, NodeFlag flag) { if ((from->user2() & flag) && !(to->user2() & flag)) { - to->user2(to->user2() | flag); + addFlags(to, flag); return true; } return false; @@ -216,6 +240,15 @@ private: } } template + void propagateFlagsIf(DepVtx* const vxp, NodeFlag flag, Predicate p) { + auto* const parentp = vxp->nodep(); + for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) { + auto* const depVxp = static_cast(edgep->top()); + AstNode* const depp = depVxp->nodep(); + if (p(edgep) && passFlag(parentp, depp, flag)) propagateFlagsIf(depVxp, flag, p); + } + } + template void propagateFlagsReversedIf(DepVtx* const vxp, NodeFlag flag, Predicate p) { auto* const parentp = vxp->nodep(); for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) { @@ -236,17 +269,22 @@ private: void visit(AstNodeProcedure* nodep) override { VL_RESTORER(m_procp); m_procp = nodep; - if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF); + getNeedsProcDepVtx(nodep); + addFlags(nodep, T_ALLOCS_PROC); if (VN_IS(nodep, Always)) { UINFO(1, "Always does " << (nodep->needProcess() ? "" : "NOT ") << "need process\n"); } iterateChildren(nodep); } + void visit(AstDisableFork* nodep) override { + visit(static_cast(nodep)); + addFlags(m_procp, T_FORCES_PROC | T_NEEDS_PROC); + } void visit(AstCFunc* nodep) override { VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF); + if (nodep->needProcess()) addFlags(nodep, T_FORCES_PROC | T_NEEDS_PROC); DepVtx* const sVxp = getSuspendDepVtx(nodep); DepVtx* const pVxp = getNeedsProcDepVtx(nodep); if (!m_classp) return; @@ -287,9 +325,12 @@ private: new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()), getSuspendDepVtx(m_procp), m_underFork ? P_FORK : P_CALL}; - if (!m_underFork) - new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()), - getNeedsProcDepVtx(m_procp), P_CALL}; + new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()), + getNeedsProcDepVtx(m_procp), P_CALL}; + + if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) { + addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC); + } iterateChildren(nodep); } @@ -301,9 +342,12 @@ private: new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep), getSuspendDepVtx(m_procp), m_underFork ? P_FORK : P_CALL}; - if (!m_underFork) - new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp), - P_CALL}; + new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp), + P_CALL}; + + if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) { + addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC); + } m_procp = nodep; m_underFork = 0; @@ -316,7 +360,7 @@ private: // so that transformForks() in V3SchedTiming gets called and // removes all forks and begins if (nodep->isTimingControl() && m_procp) { - m_procp->user2(T_SUSPENDEE | T_SUSPENDER); + addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER); m_underFork |= F_MIGHT_SUSPEND; } m_underFork |= F_MIGHT_NEED_PROC; @@ -325,7 +369,7 @@ private: void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { v3Global.setUsesTiming(); - if (m_procp) m_procp->user2(T_SUSPENDEE | T_SUSPENDER); + if (m_procp) addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER | T_NEEDS_PROC); } iterateChildren(nodep); } @@ -342,22 +386,35 @@ public: // Propagate suspendability for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { DepVtx* const depVxp = static_cast(vxp); - if (depVxp->nodep()->user2() & T_SUSPENDEE) propagateFlags(depVxp, T_SUSPENDEE); + if (hasFlags(depVxp->nodep(), T_SUSPENDEE)) propagateFlags(depVxp, T_SUSPENDEE); } if (dumpGraphLevel() >= 6) m_suspGraph.dumpDotFilePrefixed("timing_deps"); - // Propagate process + + // Propagate T_HAS_PROCESS for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { DepVtx* const depVxp = static_cast(vxp); - if (depVxp->nodep()->user2() & T_HAS_PROC) propagateFlags(depVxp, T_HAS_PROC); - } - // Propagate process downwards (from caller to callee) for suspendable calls - for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { - DepVtx* const depVxp = static_cast(vxp); - if (depVxp->nodep()->user2() & T_HAS_PROC) - propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool { - return (e->weight() != P_FORK) - && (static_cast(e->top())->nodep()->user2() & T_SUSPENDEE); + // Find processes that'll allocate VlProcess + if (hasFlags(depVxp->nodep(), T_FORCES_PROC)) { + propagateFlagsIf(depVxp, T_FORCES_PROC, [&](const V3GraphEdge* e) -> bool { + return !hasFlags(static_cast(e->fromp())->nodep(), T_ALLOCS_PROC); }); + } + // Mark nodes on paths between processes and statements that use VlProcess + if (hasFlags(depVxp->nodep(), T_NEEDS_PROC)) { + propagateFlagsIf(depVxp, T_NEEDS_PROC, [&](const V3GraphEdge* e) -> bool { + return !hasFlags(static_cast(e->top())->nodep(), T_ALLOCS_PROC); + }); + } + } + for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { + DepVtx* const depVxp = static_cast(vxp); + // Mark nodes that will be emitted with a VlProcess argument + if (hasFlags(depVxp->nodep(), T_ALLOCS_PROC | T_FORCES_PROC)) { + addFlags(depVxp->nodep(), T_HAS_PROC); + propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool { + return hasFlags(static_cast(e->fromp())->nodep(), T_NEEDS_PROC); + }); + } } if (dumpGraphLevel() >= 6) m_procGraph.dumpDotFilePrefixed("proc_deps"); } @@ -586,7 +643,7 @@ private: void addProcessInfo(AstCMethodHard* const methodp) const { FileLine* const flp = methodp->fileline(); AstCExpr* const ap = new AstCExpr{ - flp, m_procp && (m_procp->user2() & T_HAS_PROC) ? "vlProcess" : "nullptr", 0}; + flp, m_procp && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr", 0}; ap->dtypeSetVoid(); methodp->addPinsp(ap); } @@ -697,8 +754,8 @@ private: VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - if (nodep->user2() & T_SUSPENDEE) nodep->setSuspendable(); - if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); + if (hasFlags(nodep, T_SUSPENDEE)) nodep->setSuspendable(); + if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); } void visit(AstInitial* nodep) override { visit(static_cast(nodep)); @@ -719,11 +776,11 @@ private: // Workaround for killing `always` processes (doing that is pretty much UB) // TODO: Disallow killing `always` at runtime (throw an error) - if (nodep->user2() & T_HAS_PROC) nodep->user2(nodep->user2() | T_SUSPENDEE); + if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE); iterateChildren(nodep); - if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); - if (!(nodep->user2() & T_SUSPENDEE)) return; + if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); + if (!hasFlags(nodep, T_SUSPENDEE)) return; nodep->setSuspendable(); FileLine* const flp = nodep->fileline(); AstSenTree* const sensesp = m_activep->sensesp(); @@ -744,8 +801,8 @@ private: VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); - if (!(nodep->user2() & T_SUSPENDEE)) return; + if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); + if (!(hasFlags(nodep, T_SUSPENDEE))) return; nodep->rtnType("VlCoroutine"); // If in a class, create a shared pointer to 'this' @@ -768,7 +825,7 @@ private: } } void visit(AstNodeCCall* nodep) override { - if ((nodep->funcp()->user2() & T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable + if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable VNRelinker relinker; nodep->unlinkFrBack(&relinker); AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep}; @@ -1048,6 +1105,7 @@ private: void visit(AstBegin* nodep) override { VL_RESTORER(m_procp); m_procp = nodep; + if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); iterateChildren(nodep); } void visit(AstFork* nodep) override { @@ -1061,6 +1119,7 @@ private: while (stmtp) { if (!VN_IS(stmtp, Begin)) { auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr}; + if (hasFlags(stmtp, T_HAS_PROC)) addFlags(beginp, T_HAS_PROC); stmtp->replaceWith(beginp); beginp->addStmtsp(stmtp); stmtp = beginp; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index cc6f2551d..ed55d1b36 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -696,8 +696,14 @@ private: } } void visit(AstDisableFork* nodep) override { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: disable fork statements"); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + if (nodep->fileline()->timingOn()) { + if (v3Global.opt.timing().isSetFalse()) { + nodep->v3warn(E_NOTIMING, "Support for disable fork statement requires --timing"); + } else if (!v3Global.opt.timing().isSetTrue()) { + nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how " + << "disable fork should be handled"); + } + } } void visit(AstWaitFork* nodep) override { nodep->v3warn(E_UNSUPPORTED, "Unsupported: wait fork statements"); diff --git a/test_regress/t/t_disable.out b/test_regress/t/t_disable.out new file mode 100644 index 000000000..d59e8bc0f --- /dev/null +++ b/test_regress/t/t_disable.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_disable.v:11:10: Unsupported: disabling fork by name + 11 | disable foo; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_disable.pl b/test_regress/t/t_disable.pl new file mode 100755 index 000000000..e68534982 --- /dev/null +++ b/test_regress/t/t_disable.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ['--lint-only --timing'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_disable.v b/test_regress/t/t_disable.v new file mode 100644 index 000000000..78cdb2f21 --- /dev/null +++ b/test_regress/t/t_disable.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/); + + initial begin + fork : foo + disable foo; + #1 $stop; + join_none + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_disable_fork1.pl b/test_regress/t/t_disable_fork1.pl new file mode 100755 index 000000000..b8493bd06 --- /dev/null +++ b/test_regress/t/t_disable_fork1.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_disable_fork1.v b/test_regress/t/t_disable_fork1.v new file mode 100644 index 000000000..e1f5cc711 --- /dev/null +++ b/test_regress/t/t_disable_fork1.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`define N 3 + +class Cls; + task runforks(integer n); + for (integer i = 0; i < n; i++) fork + #1 $stop; + join_none + endtask +endclass + +module t; + Cls cls = new; + + initial begin + // run forks + for (integer i = 0; i < `N; i++) fork + #1 $stop; + join_none + + // run forks inside a method + cls.runforks(`N); + + // run forks in forks + for (integer i = 0; i < `N; i++) fork + for (integer j = 0; j < `N; j++) fork + #1 $stop; + join_none + join_none + + for (integer i = 0; i < `N; i++) fork + cls.runforks(`N); + join_none + + // kill them all + disable fork; + + // check if we can still fork + fork + #2 $write("*-* All Finished *-*\n"); + #3 $finish; + join_none + end +endmodule diff --git a/test_regress/t/t_disable_fork2.pl b/test_regress/t/t_disable_fork2.pl new file mode 100755 index 000000000..b8493bd06 --- /dev/null +++ b/test_regress/t/t_disable_fork2.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_disable_fork2.v b/test_regress/t/t_disable_fork2.v new file mode 100644 index 000000000..118e3e7f5 --- /dev/null +++ b/test_regress/t/t_disable_fork2.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +// USING THIS FOR DEBUGGING PROCESS PROPAGATION: +// +// The example contains most cases that were problematic during the +// works on support of 'disable fork' statement, including: +// +// - indirect use of disable fork (through a task), +// - indirect use of forks that are to be disabled, +// - forks in forks, +// - a function taking VlProcess argument shared between a process that +// allocates VlProcess, and one that doesnt, +// - a function that has a delay and obtains VlProcess argument, +// - a function that has a delay and doesn't obtain it. +// +// Blocks below contain info on whether they should (YES) or shouldn't (NO) +// be emitted as functions with a VlProcess argument. +// +// To check if that corresponds to reality, see blue nodes in proc_deps.dot + +class Cls; + task print; /*NO*/ + $write("*-* All "); + endtask + task disable_fork_func; /*YES*/ + disable fork; + endtask + task common_func; /*YES*/ + fork /*YES*/ #1; join_none + endtask + task fork_func; /*YES*/ + fork /*YES*/ #1 $stop; join_none + endtask + task delay_func; /*NO*/ + fork /*NO*/ #1 $write("Finished *-*\n"); join_none + endtask +endclass + +module t; + Cls cls = new; + + initial begin /*YES*/ + fork /*YES*/ cls.common_func(); join_none + cls.fork_func(); + cls.disable_fork_func(); + cls.print(); + end + + initial begin /*NO*/ + cls.delay_func(); + cls.common_func(); + fork /*YES*/ disable fork; join_none + end +endmodule diff --git a/test_regress/t/t_disable_fork3.pl b/test_regress/t/t_disable_fork3.pl new file mode 100755 index 000000000..b8493bd06 --- /dev/null +++ b/test_regress/t/t_disable_fork3.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_disable_fork3.v b/test_regress/t/t_disable_fork3.v new file mode 100644 index 000000000..4fc5f1759 --- /dev/null +++ b/test_regress/t/t_disable_fork3.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + fork begin + fork begin + #3 $stop; + end join_none + #1; + end join_none + #2 disable fork; + end + initial #4 $write("*-* All Finished *-*\n"); +endmodule diff --git a/test_regress/t/t_disable_fork_notiming.out b/test_regress/t/t_disable_fork_notiming.out new file mode 100644 index 000000000..63f71caef --- /dev/null +++ b/test_regress/t/t_disable_fork_notiming.out @@ -0,0 +1,6 @@ +%Error-NOTIMING: t/t_disable_fork_notiming.v:8:12: Support for disable fork statement requires --timing + : ... note: In instance 't' + 8 | initial disable fork; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_disable_fork_notiming.pl b/test_regress/t/t_disable_fork_notiming.pl new file mode 100755 index 000000000..1a013a057 --- /dev/null +++ b/test_regress/t/t_disable_fork_notiming.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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 + +scenarios(simulator => 1); + +compile( + expect_filename => $Self->{golden_filename}, + v_flags2 => ["--no-timing"], + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_disable_fork_notiming.v b/test_regress/t/t_disable_fork_notiming.v new file mode 100644 index 000000000..21ac2743e --- /dev/null +++ b/test_regress/t/t_disable_fork_notiming.v @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial disable fork; +endmodule diff --git a/test_regress/t/t_fork_disable.out b/test_regress/t/t_fork_disable.out deleted file mode 100644 index 4a245ec4d..000000000 --- a/test_regress/t/t_fork_disable.out +++ /dev/null @@ -1,10 +0,0 @@ -%Error-UNSUPPORTED: t/t_fork_disable.v:16:7: Unsupported: disable fork statements - : ... note: In instance 't' - 16 | disable fork; - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_fork_disable.v:17:7: Unsupported: wait fork statements - : ... note: In instance 't' - 17 | wait fork; - | ^~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_uvm_pkg_todo.vh b/test_regress/t/t_uvm_pkg_todo.vh index f3f21fa0d..30f708ef3 100644 --- a/test_regress/t/t_uvm_pkg_todo.vh +++ b/test_regress/t/t_uvm_pkg_todo.vh @@ -11190,9 +11190,7 @@ task uvm_phase::execute_phase(); end end join_any -//TODO issue #4125 - Support disable fork -//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:11187:12: Unsupported: disable fork statements -//TODO disable fork; + disable fork; end join end @@ -15172,9 +15170,7 @@ class uvm_heartbeat extends uvm_object; end @(m_stop_event); join_any -//TODO issue #4125 - Support disable fork -//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:15167:12: Unsupported: disable fork statements -//TODO disable fork; + disable fork; endtask endclass class uvm_heartbeat_callback extends uvm_objection_callback; @@ -19218,9 +19214,7 @@ task uvm_sequencer_base::m_wait_for_available_sequence(); join_any end join_any -//TODO issue #4125 - Support disable fork -//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:19213:12: Unsupported: disable fork statements -//TODO disable fork; + disable fork; end join endtask diff --git a/test_regress/t/t_wait_fork.out b/test_regress/t/t_wait_fork.out new file mode 100644 index 000000000..b9cf141c8 --- /dev/null +++ b/test_regress/t/t_wait_fork.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_wait_fork.v:17:7: Unsupported: wait fork statements + : ... note: In instance 't' + 17 | wait fork; + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_fork_disable.pl b/test_regress/t/t_wait_fork.pl similarity index 100% rename from test_regress/t/t_fork_disable.pl rename to test_regress/t/t_wait_fork.pl diff --git a/test_regress/t/t_fork_disable.v b/test_regress/t/t_wait_fork.v similarity index 100% rename from test_regress/t/t_fork_disable.v rename to test_regress/t/t_wait_fork.v