diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 3e4346a93..cbb894671 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -419,7 +419,7 @@ void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPoint } if (nodep->funcp()->needProcess()) { if (comma) puts(", "); - if (VN_IS(nodep->backp(), CAwait)) { + if (VN_IS(nodep->backp(), CAwait) || !nodep->funcp()->isCoroutine()) { puts("vlProcess"); } else { puts("std::make_shared()"); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 08c485167..e2f4564a7 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -22,6 +22,7 @@ #include "V3EmitCConstInit.h" #include "V3Global.h" +#include "V3MemberMap.h" #include #include @@ -115,6 +116,7 @@ public: class EmitCFunc VL_NOT_FINAL : public EmitCConstInit { private: + VMemberMap m_memberMap; AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting int m_labelNum = 0; // Next label number int m_splitSize = 0; // # of cfunc nodes placed into output file @@ -148,6 +150,22 @@ protected: bool m_useSelfForThis = false; // Replace "this" with "vlSelf" const AstNodeModule* m_modp = nullptr; // Current module being emitted const AstCFunc* m_cfuncp = nullptr; // Current function being emitted + bool m_instantiatesOwnProcess = false; + + bool constructorNeedsProcess(const AstClass* const classp) { + const AstNode* const newp = m_memberMap.findMember(classp, "new"); + if (!newp) return false; + const AstCFunc* const ctorp = VN_CAST(newp, CFunc); + if (!ctorp) return false; + UASSERT_OBJ(ctorp->isConstructor(), ctorp, "`new` is not a constructor!"); + return ctorp->needProcess(); + } + + bool constructorNeedsProcess(const AstNodeDType* const dtypep) { + if (const AstClassRefDType* const crefdtypep = VN_CAST(dtypep, ClassRefDType)) + return constructorNeedsProcess(crefdtypep->classp()); + return false; + } public: // METHODS @@ -212,13 +230,19 @@ public: comma = true; } } + template + string optionalProcArg(const T* const nodep) { + return (nodep && constructorNeedsProcess(nodep)) ? "vlProcess, " : ""; + } // VISITORS using EmitCConstInit::visit; void visit(AstCFunc* nodep) override { VL_RESTORER(m_useSelfForThis); VL_RESTORER(m_cfuncp); + VL_RESTORER(m_instantiatesOwnProcess) m_cfuncp = nodep; + m_instantiatesOwnProcess = false; splitSizeInc(nodep); @@ -231,7 +255,8 @@ public: if (!nodep->baseCtors().empty()) { puts(": "); puts(nodep->baseCtors()); - puts("(vlSymsp"); + const AstClass* const classp = VN_CAST(nodep->scopep()->modp(), Class); + bool baseCtorCall = false; // Find call to super.new to get the arguments for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { AstNode* exprp; @@ -241,11 +266,14 @@ public: exprp = stmtp; } if (AstCNew* const newRefp = VN_CAST(exprp, CNew)) { + puts("(" + optionalProcArg(classp) + "vlSymsp"); + baseCtorCall = true; putCommaIterateNext(newRefp->argsp(), true); + puts(")"); break; } } - puts(")"); + if (!baseCtorCall) { puts("(" + optionalProcArg(classp) + "vlSymsp)"); } } puts(" {\n"); @@ -265,6 +293,23 @@ public: puts(nodep->isLoose() ? "__" : "::"); puts(nodep->nameProtect() + "\\n\"); );\n"); + // Instantiate a process class if it's going to be needed somewhere later + nodep->forall([&](const AstNodeCCall* ccallp) -> bool { + if (ccallp->funcp()->needProcess() + && (ccallp->funcp()->isCoroutine() == VN_IS(ccallp->backp(), CAwait))) { + if (!nodep->needProcess() && !m_instantiatesOwnProcess) { + m_instantiatesOwnProcess = true; + return false; + } + } + return true; + }); + if (m_instantiatesOwnProcess) { + AstNode* const vlprocp = new AstCStmt{ + nodep->fileline(), "VlProcessRef vlProcess = std::make_shared();\n"}; + nodep->stmtsp()->addHereThisAsNext(vlprocp); + } + for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) { if (AstVar* const varp = VN_CAST(subnodep, Var)) { if (varp->isFuncReturn()) emitVarDecl(varp); @@ -441,8 +486,9 @@ public: // super.new case return; } - // assignment case - puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", vlSymsp"); + // assignment case; + puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", " + + optionalProcArg(nodep->dtypep()) + "vlSymsp"); putCommaIterateNext(nodep->argsp(), true); puts(")"); } @@ -1087,7 +1133,8 @@ public: puts(")"); } void visit(AstNewCopy* nodep) override { - puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", "); + puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", " + + optionalProcArg(nodep->dtypep())); puts("*"); // i.e. make into a reference iterateAndNextConstNull(nodep->rhsp()); puts(")"); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index a725d3e82..28c561c95 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -274,7 +274,6 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { subFuncp = createNewSubFuncp(scopep); subFuncp->name(subFuncp->name() + "__" + cvtToStr(scopep->user2Inc())); subFuncp->rtnType("VlCoroutine"); - if (procp->needProcess()) subFuncp->setNeedProcess(); if (VN_IS(procp, Always)) { subFuncp->slow(false); FileLine* const flp = procp->fileline(); @@ -283,6 +282,7 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { } } subFuncp->addStmtsp(bodyp); + if (procp->needProcess()) subFuncp->setNeedProcess(); splitCheck(subFuncp); } } else { diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index e3b6ffaca..b78510fe4 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -64,41 +64,85 @@ VL_DEFINE_DEBUG_FUNCTIONS; // ###################################################################### -enum TimingFlag : uint8_t { - // Properties of flags with higher numbers include properties of flags with - // lower numbers - T_NORM = 0, // Normal non-suspendable process - T_SUSP = 1, // Suspendable - T_PROC = 2 // Suspendable with process metadata +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 +}; + +enum ForkType : uint8_t { + F_NONE = 0, // Not under a fork + F_MIGHT_SUSPEND = 1 << 0, // Fork might suspend the execution of current process + F_MIGHT_NEED_PROC = 1 << 1, // Fork might need a process (any fork really) +}; + +enum PropagationType : uint8_t { + P_CALL = 1, // Propagation through call to a function/task/method + P_FORK = 2, // Propagation due to fork's behaviour + P_SIGNATURE = 3, // Propagation required to maintain C++ function's signature requirements }; // ###################################################################### -// Detect nodes affected by timing +// Detect nodes affected by timing and/or requiring a process class TimingSuspendableVisitor final : public VNVisitor { private: // TYPES // Vertex of a dependency graph of suspendable nodes, e.g. if a node (process or task) is // suspendable, all its dependents should also be suspendable - class TimingDependencyVertex final : public V3GraphVertex { + class DepVtx VL_NOT_FINAL : public V3GraphVertex { + AstClass* const m_classp; // Class associated with a method AstNode* const m_nodep; // AST node represented by this graph vertex // ACCESSORS string name() const override VL_MT_STABLE { + if (m_classp) { + if (VN_IS(nodep(), CFunc)) { + return cvtToHex(nodep()) + ' ' + classp()->name() + "::" + nodep()->name(); + } + } return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName(); } FileLine* fileline() const override { return nodep()->fileline(); } - string dotColor() const override { return nodep()->user2() ? "red" : "black"; } public: // CONSTRUCTORS - TimingDependencyVertex(V3Graph* graphp, AstNode* nodep) + DepVtx(V3Graph* graphp, AstNode* nodep, AstClass* classp) : V3GraphVertex{graphp} + , m_classp{classp} , m_nodep{nodep} {} - ~TimingDependencyVertex() override = default; + ~DepVtx() override = default; // ACCESSORS virtual AstNode* nodep() const VL_MT_STABLE { return m_nodep; } + virtual AstNode* classp() const VL_MT_STABLE { return m_classp; } + }; + + class SuspendDepVtx final : public DepVtx { + string dotColor() const override { + if (nodep()->user2() & T_SUSPENDER) return "red"; + if (nodep()->user2() & T_SUSPENDEE) return "blue"; + return "black"; + } + + public: + SuspendDepVtx(V3Graph* graphp, AstNode* nodep, AstClass* classp) + : DepVtx{graphp, nodep, classp} {} + ~SuspendDepVtx() override = default; + }; + + class NeedsProcDepVtx final : public DepVtx { + string dotColor() const override { + if (nodep()->user2() & T_CALLS_PROC_SELF) return "red"; + if (nodep()->user2() & T_HAS_PROC) return "blue"; + return "black"; + } + + public: + NeedsProcDepVtx(V3Graph* graphp, AstNode* nodep, AstClass* classp) + : DepVtx{graphp, nodep, classp} {} + ~NeedsProcDepVtx() override = default; }; // NODE STATE @@ -109,41 +153,70 @@ private: // process/task suspendable // and to T_PROC if it // needs process metadata. - // Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph + // Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_suspGraph + // Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_procGraph const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; const VNUser3InUse m_user3InUse; + const VNUser5InUse m_user5InUse; // STATE VMemberMap memberMap; // Member names cached for fast lookup AstClass* m_classp = nullptr; // Current class AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under - V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being - // suspendable makes the other node suspendable + uint8_t m_underFork = F_NONE; // F_NONE or flags of a fork we are under + V3Graph m_suspGraph; // Dependency graph where a node is a dependency of another if it being + // suspendable makes the other node suspendable + V3Graph m_procGraph; // Dependency graph where a node is a dependency of another if it being + // suspendable makes the other node suspendable // METHODS // Get or create the dependency vertex for the given node - TimingDependencyVertex* getDependencyVertex(AstNode* const nodep) { - if (!nodep->user3p()) nodep->user3p(new TimingDependencyVertex{&m_depGraph, nodep}); - return nodep->user3u().to(); + DepVtx* getSuspendDepVtx(AstNode* const nodep) { + AstClass* classp = nullptr; + if (AstCFunc* funcp = VN_CAST(nodep, CFunc)) { + if (funcp->scopep() && funcp->scopep()->modp()) { + classp = VN_CAST(funcp->scopep()->modp(), Class); + } + } + if (!nodep->user3p()) nodep->user3p(new SuspendDepVtx{&m_suspGraph, nodep, classp}); + return nodep->user3u().to(); + } + DepVtx* getNeedsProcDepVtx(AstNode* const nodep) { + AstClass* classp = nullptr; + if (AstCFunc* funcp = VN_CAST(nodep, CFunc)) { + if (funcp->scopep() && funcp->scopep()->modp()) { + classp = VN_CAST(funcp->scopep()->modp(), Class); + } + } + if (!nodep->user5p()) nodep->user5p(new NeedsProcDepVtx{&m_procGraph, nodep, classp}); + return nodep->user5u().to(); } // Set timing flag of a node - bool setTimingFlag(AstNode* nodep, int flag) { - // Properties of flags with higher numbers include properties of flags with lower - // numbers, so modify nodep->user2() only if it will increase. - if (nodep->user2() < flag) { - nodep->user2(flag); + bool passFlag(const AstNode* from, AstNode* to, NodeFlag flag) { + if ((from->user2() & flag) && !(to->user2() & flag)) { + to->user2(to->user2() | flag); return true; } return false; } - // Propagate suspendable/needProcess flag to all nodes that depend on the given one - void propagateTimingFlags(TimingDependencyVertex* const vxp) { + // Propagate flag to all nodes that depend on the given one + void propagateFlags(DepVtx* const vxp, NodeFlag flag) { + 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 (passFlag(parentp, depp, flag)) propagateFlags(depVxp, flag); + } + } + template + void propagateFlagsReversedIf(DepVtx* const vxp, NodeFlag flag, Predicate p) { auto* const parentp = vxp->nodep(); for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) { - auto* const depVxp = static_cast(edgep->fromp()); + auto* const depVxp = static_cast(edgep->fromp()); AstNode* const depp = depVxp->nodep(); - if (setTimingFlag(depp, parentp->user2())) propagateTimingFlags(depVxp); + if (p(edgep) && passFlag(parentp, depp, flag)) + propagateFlagsReversedIf(depVxp, flag, p); } } @@ -157,14 +230,19 @@ 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); + if (VN_IS(nodep, Always)) { + UINFO(1, "Always does " << (nodep->needProcess() ? "" : "NOT ") << "need process\n"); + } iterateChildren(nodep); } void visit(AstCFunc* nodep) override { VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - TimingDependencyVertex* const vxp = getDependencyVertex(nodep); - if (nodep->needProcess()) nodep->user2(T_PROC); + if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF); + DepVtx* const sVxp = getSuspendDepVtx(nodep); + DepVtx* const pVxp = getNeedsProcDepVtx(nodep); if (!m_classp) return; // Go over overridden functions @@ -184,16 +262,13 @@ private: // virtual or not. if (auto* const overriddenp = VN_CAST(memberMap.findMember(cextp->classp(), nodep->name()), CFunc)) { - setTimingFlag(nodep, overriddenp->user2()); - if (nodep->user2() - < T_PROC) { // Add a vertex only if the flag can still change - // Make a dependency cycle, as being suspendable should propagate both up - // and down the inheritance tree - TimingDependencyVertex* const overriddenVxp - = getDependencyVertex(overriddenp); - new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1}; - new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1}; - } + // Suspendability and process affects typing, so they propagate both ways + DepVtx* const overriddenSVxp = getSuspendDepVtx(overriddenp); + DepVtx* const overriddenPVxp = getNeedsProcDepVtx(overriddenp); + new V3GraphEdge{&m_suspGraph, sVxp, overriddenSVxp, P_SIGNATURE}; + new V3GraphEdge{&m_suspGraph, overriddenSVxp, sVxp, P_SIGNATURE}; + new V3GraphEdge{&m_procGraph, pVxp, overriddenPVxp, P_SIGNATURE}; + new V3GraphEdge{&m_procGraph, overriddenPVxp, pVxp, P_SIGNATURE}; } else { AstClassExtends* more_extends = cextp->classp()->extendsp(); if (more_extends) extends.push(more_extends); @@ -202,30 +277,49 @@ private: } } void visit(AstNodeCCall* nodep) override { - setTimingFlag(m_procp, nodep->funcp()->user2()); - if (m_procp->user2() < T_PROC) { // Add a vertex only if the flag can still change - TimingDependencyVertex* const procVxp = getDependencyVertex(m_procp); - TimingDependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp()); - new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1}; - iterateChildren(nodep); - } + if (!m_underFork || (m_underFork & F_MIGHT_SUSPEND)) + 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}; + + iterateChildren(nodep); } void visit(AstBegin* nodep) override { VL_RESTORER(m_procp); + VL_RESTORER(m_underFork); + + if (!m_underFork || (m_underFork & F_MIGHT_SUSPEND)) + 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}; + m_procp = nodep; + m_underFork = 0; iterateChildren(nodep); } void visit(AstFork* nodep) override { + VL_RESTORER(m_underFork); + v3Global.setUsesTiming(); // Even if there are no event controls, we have to set this flag // so that transformForks() in V3SchedTiming gets called and // removes all forks and begins - if (nodep->isTimingControl() && m_procp) m_procp->user2(T_SUSP); + if (nodep->isTimingControl() && m_procp) { + m_procp->user2(T_SUSPENDEE | T_SUSPENDER); + m_underFork |= F_MIGHT_SUSPEND; + } + m_underFork |= F_MIGHT_NEED_PROC; iterateChildren(nodep); } void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { v3Global.setUsesTiming(); - if (m_procp) m_procp->user2(T_SUSP); + if (m_procp) m_procp->user2(T_SUSPENDEE | T_SUSPENDER); } iterateChildren(nodep); } @@ -237,12 +331,29 @@ public: // CONSTRUCTORS explicit TimingSuspendableVisitor(AstNetlist* nodep) { iterate(nodep); - m_depGraph.removeTransitiveEdges(); - for (V3GraphVertex* vxp = m_depGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { - TimingDependencyVertex* const depVxp = static_cast(vxp); - if (depVxp->nodep()->user2()) propagateTimingFlags(depVxp); + m_suspGraph.removeTransitiveEdges(); + m_procGraph.removeTransitiveEdges(); + // 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 (dumpGraphLevel() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps"); + if (dumpGraphLevel() >= 6) m_suspGraph.dumpDotFilePrefixed("timing_deps"); + // Propagate 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); + }); + } + if (dumpGraphLevel() >= 6) m_procGraph.dumpDotFilePrefixed("proc_deps"); } ~TimingSuspendableVisitor() override = default; }; @@ -488,7 +599,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_PROC ? "vlProcess" : "nullptr", 0}; + flp, m_procp && (m_procp->user2() & T_HAS_PROC) ? "vlProcess" : "nullptr", 0}; ap->dtypeSetVoid(); methodp->addPinsp(ap); } @@ -587,8 +698,8 @@ private: VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - if (nodep->user2() >= T_SUSP) nodep->setSuspendable(); - if (nodep->user2() >= T_PROC) nodep->setNeedProcess(); + if (nodep->user2() & T_SUSPENDEE) nodep->setSuspendable(); + if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); } void visit(AstInitial* nodep) override { visit(static_cast(nodep)); @@ -601,9 +712,14 @@ private: if (nodep->user1SetOnce()) return; VL_RESTORER(m_procp); m_procp = nodep; + + // 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); + iterateChildren(nodep); - if (!nodep->user2()) return; - if (nodep->user2() == T_PROC) nodep->setNeedProcess(); + if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); + if (!(nodep->user2() & T_SUSPENDEE)) return; nodep->setSuspendable(); FileLine* const flp = nodep->fileline(); AstSenTree* const sensesp = m_activep->sensesp(); @@ -624,7 +740,9 @@ private: VL_RESTORER(m_procp); m_procp = nodep; iterateChildren(nodep); - if (!nodep->user2()) return; + if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess(); + if (!(nodep->user2() & T_SUSPENDEE)) return; + nodep->rtnType("VlCoroutine"); // If in a class, create a shared pointer to 'this' if (m_classp) nodep->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"}); @@ -644,10 +762,9 @@ private: firstCoStmtp->v3warn(E_UNSUPPORTED, "Unsupported: Timing controls inside DPI-exported tasks"); } - if (nodep->user2() == T_PROC) nodep->setNeedProcess(); } void visit(AstNodeCCall* nodep) override { - if (nodep->funcp()->user2() && !nodep->user1SetOnce()) { // If suspendable + if ((nodep->funcp()->user2() & T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable VNRelinker relinker; nodep->unlinkFrBack(&relinker); AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep}; diff --git a/test_regress/t/t_process_propagation.pl b/test_regress/t/t_process_propagation.pl new file mode 100755 index 000000000..3364f1ed7 --- /dev/null +++ b/test_regress/t/t_process_propagation.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 2003 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_process_propagation.v b/test_regress/t/t_process_propagation.v new file mode 100644 index 000000000..812b12ccb --- /dev/null +++ b/test_regress/t/t_process_propagation.v @@ -0,0 +1,68 @@ +// 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 + +event evt1, evt2; + +class Foo; + process p; + bit event_received; + + function new(); + p = process::self(); + endfunction + + virtual task ewait(); + @evt1 $display("Foo received event `evt1`"); + event_received = 1; + ->evt2; + endtask +endclass + +class Bar extends Foo; + function new(); + super.new(); + $display("Constructing Bar"); + endfunction + + virtual task ewait(); + @evt1 $display("Bar received event `evt1`"); + event_received = 1; + endtask +endclass + +module t(); + initial begin + process p; + Foo foo; + Bar bar; + + fork + begin + foo = new; + foo.ewait(); + end + begin + bar = new; + p = process::self(); + bar.ewait(); + end + join_none + + p.kill(); + + ->evt1; + + @evt2 begin + if (!foo.event_received) + $stop; + if (bar.event_received) + $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule