diff --git a/include/verilated_std.sv b/include/verilated_std.sv index 9a1153c76..bb7fe1be8 100644 --- a/include/verilated_std.sv +++ b/include/verilated_std.sv @@ -26,10 +26,6 @@ // verilator lint_off TIMESCALEMOD // verilator lint_off UNUSEDSIGNAL package std; - // The process class is not implemented, but it's predeclared here, - // so the linter accepts references to it. - typedef class process; - class mailbox #(type T); protected int m_bound; protected T m_queue[$]; @@ -45,7 +41,7 @@ package std; task put(T message); `ifdef VERILATOR_TIMING if (m_bound != 0) - wait (m_queue.size() < m_bound); + wait (m_queue.size() < m_bound); m_queue.push_back(message); `endif endtask @@ -117,43 +113,80 @@ package std; endclass class process; - typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state; - static process _s_global_process; + typedef enum { + FINISHED = 0, + RUNNING = 1, + WAITING = 2, + SUSPENDED = 3, + KILLED = 4 + } state; + +`ifdef VERILATOR_TIMING + // Width visitor changes it to VlProcessRef + protected chandle m_process; +`endif + static function process self(); - // Unsupported, emulating with single process' state - if (!_s_global_process) _s_global_process = new; - return _s_global_process; + process p = new; +`ifdef VERILATOR_TIMING + $c(p.m_process, " = vlProcess;"); +`endif + return p; endfunction + + protected function void set_status(state s); +`ifdef VERILATOR_TIMING + $c(m_process, "->state(", s, ");"); +`endif + endfunction + function state status(); - // Unsupported, emulating with single process' state +`ifdef VERILATOR_TIMING + return state'($c(m_process, "->state()")); +`else return RUNNING; +`endif endfunction + function void kill(); - $error("std::process::kill() not supported"); + set_status(KILLED); endfunction - task await(); - $error("std::process::await() not supported"); - endtask + function void suspend(); $error("std::process::suspend() not supported"); endfunction + function void resume(); - $error("std::process::resume() not supported"); + set_status(RUNNING); endfunction - // When really implemented, srandom must operates on the process, but for + + task await(); +`ifdef VERILATOR_TIMING + wait (status() == FINISHED || status() == KILLED); +`endif + endtask + + // When really implemented, srandom must operate on the process, but for // now rely on the srandom() that is automatically generated for all // classes. + // // function void srandom(int seed); // endfunction + + // The methods below work only if set_randstate is never applied to + // a state string created before another such string. Full support + // could use VlRNG class to store the state per process in VlProcess + // objects. function string get_randstate(); - // Could operate on all proceses for now - // No error, as harmless until set_randstate is called - return "NOT_SUPPORTED"; + string s; + + s.itoa($random); // Get a random number + set_randstate(s); // Pretend it's the state of RNG + return s; endfunction - function void set_randstate(string randstate); - $error("std::process::set_randstate() not supported"); - // Could operate on all proceses for now + + function void set_randstate(string s); + $urandom(s.atoi()); // Set the seed using a string endfunction endclass - endpackage diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index d3fa2cf1b..30b3fbf7f 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -30,7 +30,16 @@ void VlCoroutineHandle::resume() { // main process if (VL_LIKELY(m_coro)) { VL_DEBUG_IF(VL_DBG_MSGF(" Resuming: "); dump();); - m_coro(); + if (m_process) { // If process state is managed with std::process + if (m_process->state() == VlProcess::KILLED) { + m_coro.destroy(); + } else { + m_process->state(VlProcess::RUNNING); + m_coro(); + } + } else { + m_coro(); + } m_coro = nullptr; } } diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 14d570449..1e63009c5 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -86,6 +86,36 @@ public: #endif }; +//=================================================================== +// VlProcess stores metadata of running processes + +class VlProcess final { + // MEMBERS + int m_state; // Current state of the process + +public: + // TYPES + enum : int { // Type int for compatibility with $c + FINISHED = 0, + RUNNING = 1, + WAITING = 2, + SUSPENDED = 3, + KILLED = 4, + }; + + // CONSTRUCTORS + VlProcess() + : m_state{RUNNING} {} + + // METHODS + int state() { return m_state; } + void state(int s) { m_state = s; } +}; + +using VlProcessRef = std::shared_ptr; + +inline std::string VL_TO_STRING(const VlProcessRef& p) { return std::string("process"); } + //============================================================================= // VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is // cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got @@ -96,25 +126,38 @@ class VlCoroutineHandle final { // MEMBERS std::coroutine_handle<> m_coro; // The wrapped coroutine handle + VlProcessRef m_process; // Data of the suspended process, null if not needed VlFileLineDebug m_fileline; public: // CONSTRUCTORS // Construct - VlCoroutineHandle() - : m_coro{nullptr} {} - VlCoroutineHandle(std::coroutine_handle<> coro, VlFileLineDebug fileline) + VlCoroutineHandle(VlProcessRef process) + : m_coro{nullptr} + , m_process{process} { + if (m_process) m_process->state(VlProcess::WAITING); + } + VlCoroutineHandle(std::coroutine_handle<> coro, VlProcessRef process, VlFileLineDebug fileline) : m_coro{coro} - , m_fileline{fileline} {} + , m_process{process} + , m_fileline{fileline} { + if (m_process) m_process->state(VlProcess::WAITING); + } // Move the handle, leaving a nullptr VlCoroutineHandle(VlCoroutineHandle&& moved) : m_coro{std::exchange(moved.m_coro, nullptr)} + , m_process{moved.m_process} , m_fileline{moved.m_fileline} {} // Destroy if the handle isn't null ~VlCoroutineHandle() { // Usually these coroutines should get resumed; we only need to clean up if we destroy a // model with some coroutines suspended - if (VL_UNLIKELY(m_coro)) m_coro.destroy(); + if (VL_UNLIKELY(m_coro)) { + m_coro.destroy(); + if (m_process && m_process->state() != VlProcess::KILLED) { + m_process->state(VlProcess::FINISHED); + } + } } // METHODS // Move the handle, leaving a null handle @@ -122,7 +165,7 @@ public: m_coro = std::exchange(moved.m_coro, nullptr); return *this; } - // Resume the coroutine if the handle isn't null + // Resume the coroutine if the handle isn't null and the process isn't killed void resume(); #ifdef VL_DEBUG void dump() const; @@ -173,21 +216,24 @@ public: void dump() const; #endif // Used by coroutines for co_awaiting a certain simulation time - auto delay(uint64_t delay, const char* filename = VL_UNKNOWN, int lineno = 0) { + auto delay(uint64_t delay, VlProcessRef process, const char* filename = VL_UNKNOWN, + int lineno = 0) { struct Awaitable { + VlProcessRef process; // Data of the suspended process, null if not needed VlDelayedCoroutineQueue& queue; uint64_t delay; VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { - queue.push_back({delay, VlCoroutineHandle{coro, fileline}}); + queue.push_back({delay, VlCoroutineHandle{coro, process, fileline}}); // Move last element to the proper place in the max-heap std::push_heap(queue.begin(), queue.end()); } void await_resume() const {} }; - return Awaitable{m_queue, m_context.time() + delay, VlFileLineDebug{filename, lineno}}; + return Awaitable{process, m_queue, m_context.time() + delay, + VlFileLineDebug{filename, lineno}}; } }; @@ -224,21 +270,23 @@ public: void dump(const char* eventDescription) const; #endif // Used by coroutines for co_awaiting a certain trigger - auto trigger(bool commit, const char* eventDescription = VL_UNKNOWN, + auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN, const char* filename = VL_UNKNOWN, int lineno = 0) { VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", eventDescription, filename, lineno);); struct Awaitable { VlCoroutineVec& suspended; // Coros waiting on trigger + VlProcessRef process; // Data of the suspended process, null if not needed VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { - suspended.emplace_back(coro, fileline); + suspended.emplace_back(coro, process, fileline); } void await_resume() const {} }; - return Awaitable{commit ? m_ready : m_uncommitted, VlFileLineDebug{filename, lineno}}; + return Awaitable{commit ? m_ready : m_uncommitted, process, + VlFileLineDebug{filename, lineno}}; } }; @@ -271,18 +319,19 @@ class VlDynamicTriggerScheduler final { // with destructive post updates, e.g. named events) // METHODS - auto awaitable(VlCoroutineVec& queue, const char* filename, int lineno) { + auto awaitable(VlProcessRef process, VlCoroutineVec& queue, const char* filename, int lineno) { struct Awaitable { + VlProcessRef process; // Data of the suspended process, null if not needed VlCoroutineVec& suspended; // Coros waiting on trigger VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { - suspended.emplace_back(coro, fileline); + suspended.emplace_back(coro, process, fileline); } void await_resume() const {} }; - return Awaitable{queue, VlFileLineDebug{filename, lineno}}; + return Awaitable{process, queue, VlFileLineDebug{filename, lineno}}; } public: @@ -296,23 +345,26 @@ public: void dump() const; #endif // Used by coroutines for co_awaiting trigger evaluation - auto evaluation(const char* eventDescription, const char* filename, int lineno) { + auto evaluation(VlProcessRef process, const char* eventDescription, const char* filename, + int lineno) { VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", eventDescription, filename, lineno);); - return awaitable(m_suspended, filename, lineno); + return awaitable(process, m_suspended, filename, lineno); } // Used by coroutines for co_awaiting the trigger post update step - auto postUpdate(const char* eventDescription, const char* filename, int lineno) { + auto postUpdate(VlProcessRef process, const char* eventDescription, const char* filename, + int lineno) { VL_DEBUG_IF( VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting the post update step\n", eventDescription, filename, lineno);); - return awaitable(m_post, filename, lineno); + return awaitable(process, m_post, filename, lineno); } // Used by coroutines for co_awaiting the resumption step (in 'act' eval) - auto resumption(const char* eventDescription, const char* filename, int lineno) { + auto resumption(VlProcessRef process, const char* eventDescription, const char* filename, + int lineno) { VL_DEBUG_IF(VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting resumption\n", eventDescription, filename, lineno);); - return awaitable(m_triggered, filename, lineno); + return awaitable(process, m_triggered, filename, lineno); } }; @@ -342,24 +394,27 @@ class VlForkSync final { public: // Create the join object and set the counter to the specified number - void init(size_t count) { m_join.reset(new VlJoin{count, {}}); } + void init(size_t count, VlProcessRef process) { m_join.reset(new VlJoin{count, {process}}); } // Called whenever any of the forked processes finishes. If the join counter reaches 0, the // main process gets resumed void done(const char* filename = VL_UNKNOWN, int lineno = 0); // Used by coroutines for co_awaiting a join - auto join(const char* filename = VL_UNKNOWN, int lineno = 0) { + auto join(VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) { assert(m_join); VL_DEBUG_IF( VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, lineno);); struct Awaitable { + VlProcessRef process; // Data of the suspended process, null if not needed const std::shared_ptr join; // Join to await on VlFileLineDebug fileline; bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists - void await_suspend(std::coroutine_handle<> coro) { join->m_susp = {coro, fileline}; } + void await_suspend(std::coroutine_handle<> coro) { + join->m_susp = {coro, process, fileline}; + } void await_resume() const {} }; - return Awaitable{m_join, VlFileLineDebug{filename, lineno}}; + return Awaitable{process, m_join, VlFileLineDebug{filename, lineno}}; } }; diff --git a/src/V3Ast.h b/src/V3Ast.h index 960a3c69a..b871314c0 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -461,6 +461,7 @@ public: TRIGGER_SCHEDULER, DYNAMIC_TRIGGER_SCHEDULER, FORK_SYNC, + PROCESS_REFERENCE, // Unsigned and two state; fundamental types UINT32, UINT64, @@ -493,6 +494,7 @@ public: "VlTriggerScheduler", "VlDynamicTriggerScheduler", "VlFork", + "VlProcessRef", "IData", "QData", "LOGIC_IMPLICIT", @@ -500,13 +502,20 @@ public: return names[m_e]; } const char* dpiType() const { - static const char* const names[] - = {"%E-unk", "svBit", "char", "void*", "char", - "int", "%E-integer", "svLogic", "long long", "double", - "short", "%E-time", "const char*", "%E-untyped", "dpiScope", - "const char*", "%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched", - "%E-dyn-sched", "%E-fork", "IData", "QData", "%E-logic-implct", - " MAX"}; + static const char* const names[] = {"%E-unk", "svBit", + "char", "void*", + "char", "int", + "%E-integer", "svLogic", + "long long", "double", + "short", "%E-time", + "const char*", "%E-untyped", + "dpiScope", "const char*", + "%E-mtaskstate", "%E-triggervec", + "%E-dly-sched", "%E-trig-sched", + "%E-dyn-sched", "%E-fork", + "%E-proc-ref", "IData", + "QData", "%E-logic-implct", + " MAX"}; return names[m_e]; } static void selfTest() { @@ -545,6 +554,7 @@ public: case TRIGGER_SCHEDULER: return 0; // opaque case DYNAMIC_TRIGGER_SCHEDULER: return 0; // opaque case FORK_SYNC: return 0; // opaque + case PROCESS_REFERENCE: return 0; // opaque case UINT32: return 32; case UINT64: return 64; default: return 0; @@ -584,7 +594,7 @@ public: return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC - || m_e == DOUBLE || m_e == UNTYPED); + || m_e == PROCESS_REFERENCE || m_e == DOUBLE || m_e == UNTYPED); } bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; } bool isEvent() const { return m_e == EVENT; } diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index c06e761c0..a74eeec6f 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -438,6 +438,7 @@ public: bool isEvent() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::EVENT; } bool isTriggerVec() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::TRIGGERVEC; } bool isForkSync() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::FORK_SYNC; } + bool isProcessRef() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::PROCESS_REFERENCE; } bool isDelayScheduler() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1070c83f9..497b545cb 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -86,6 +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_fromStd : 1; // Part of std VLifetime m_lifetime; // Lifetime protected: AstNodeFTask(VNType t, FileLine* fl, const string& name, AstNode* stmtsp) @@ -110,7 +111,8 @@ protected: , m_pureVirtual{false} , m_recursive{false} , m_underGenerate{false} - , m_virtual{false} { + , m_virtual{false} + , m_fromStd{false} { addStmtsp(stmtsp); cname(name); // Might be overridden by dpi import/export } @@ -170,6 +172,8 @@ public: bool underGenerate() const { return m_underGenerate; } void isVirtual(bool flag) { m_virtual = flag; } bool isVirtual() const { return m_virtual; } + void isFromStd(bool flag) { m_fromStd = flag; } + bool isFromStd() const { return m_fromStd; } void lifetime(const VLifetime& flag) { m_lifetime = flag; } VLifetime lifetime() const { return m_lifetime; } bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } @@ -272,10 +276,13 @@ public: 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 = false; // Is suspendable by a Delay, EventControl, etc. + bool m_suspendable : 1; // Is suspendable by a Delay, EventControl, etc. + bool m_needProcess : 1; // Implements part of a process that allocates std::process protected: AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp) : AstNode{t, fl} { + m_needProcess = false; + m_suspendable = false; addStmtsp(stmtsp); } @@ -286,6 +293,8 @@ public: bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); } bool isSuspendable() const { return m_suspendable; } void setSuspendable() { m_suspendable = true; } + bool needProcess() const { return m_needProcess; } + void setNeedProcess() { m_needProcess = true; } }; class AstNodeRange VL_NOT_FINAL : public AstNode { // A range, sized or unsized @@ -576,6 +585,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 public: AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType = "") : ASTGEN_SUPER_CFunc(fl) { @@ -595,6 +605,7 @@ public: m_isLoose = false; m_isInline = false; m_isVirtual = false; + m_needProcess = false; m_entryPoint = false; m_pure = false; m_dpiContext = false; @@ -663,6 +674,8 @@ public: void isInline(bool flag) { m_isInline = flag; } bool isVirtual() const { return m_isVirtual; } void isVirtual(bool flag) { m_isVirtual = flag; } + bool needProcess() const { return m_needProcess; } + void setNeedProcess() { m_needProcess = true; } bool entryPoint() const { return m_entryPoint; } void entryPoint(bool flag) { m_entryPoint = flag; } bool pure() const { return m_pure; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index eccb2d504..ab7225d87 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -784,6 +784,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const VL_M info.m_type = "VlDynamicTriggerScheduler"; } else if (bdtypep->isForkSync()) { info.m_type = "VlForkSync"; + } else if (bdtypep->isProcessRef()) { + info.m_type = "VlProcessRef"; } else if (bdtypep->isEvent()) { info.m_type = "VlEvent"; } else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width @@ -1352,6 +1354,7 @@ void AstNode::dump(std::ostream& str) const { void AstNodeProcedure::dump(std::ostream& str) const { this->AstNode::dump(str); if (isSuspendable()) str << " [SUSP]"; + if (needProcess()) str << " [NPRC]"; } void AstAlways::dump(std::ostream& str) const { @@ -2294,6 +2297,7 @@ void AstCFunc::dump(std::ostream& str) const { if (isDestructor()) str << " [DTOR]"; if (isVirtual()) str << " [VIRT]"; if (isCoroutine()) str << " [CORO]"; + if (needProcess()) str << " [NPRC]"; } const char* AstCAwait::broken() const { BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists()); diff --git a/src/V3Cast.cpp b/src/V3Cast.cpp index b976b5324..eaecd7320 100644 --- a/src/V3Cast.cpp +++ b/src/V3Cast.cpp @@ -186,7 +186,8 @@ private: && !VN_IS(backp, NodeCCall) && !VN_IS(backp, CMethodHard) && !VN_IS(backp, SFormatF) && !VN_IS(backp, ArraySel) && !VN_IS(backp, StructSel) && !VN_IS(backp, RedXor) && (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec() - && !nodep->varp()->basicp()->isForkSync()) + && !nodep->varp()->basicp()->isForkSync() + && !nodep->varp()->basicp()->isProcessRef()) && backp->width() && castSize(nodep) != castSize(nodep->varp())) { // Cast vars to IData first, else below has upper bits wrongly set // CData x=3; out = (QData)(x<<30); diff --git a/src/V3EmitCBase.cpp b/src/V3EmitCBase.cpp index a7779be62..5a173f38b 100644 --- a/src/V3EmitCBase.cpp +++ b/src/V3EmitCBase.cpp @@ -79,6 +79,10 @@ string EmitCBaseVisitorConst::cFuncArgs(const AstCFunc* nodep) { args += prefixNameProtect(EmitCParentModule::get(nodep)); args += "* vlSelf"; } + if (nodep->needProcess()) { + if (!args.empty()) args += ", "; + args += "VlProcessRef vlProcess"; + } if (!nodep->argTypes().empty()) { if (!args.empty()) args += ", "; args += nodep->argTypes(); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 9160e5068..195e41ea8 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -417,6 +417,15 @@ void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPoint puts(selfPointer); comma = true; } + if (nodep->funcp()->needProcess()) { + if (comma) puts(", "); + if (VN_IS(nodep->backp(), CAwait)) { + puts("vlProcess"); + } else { + puts("std::make_shared()"); + } + comma = true; + } if (!nodep->argTypes().empty()) { if (comma) puts(", "); puts(nodep->argTypes()); @@ -695,6 +704,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP return ""; } else if (basicp && basicp->isForkSync()) { return ""; + } else if (basicp && basicp->isProcessRef()) { + return ""; } else if (basicp && basicp->isDelayScheduler()) { return ""; } else if (basicp && basicp->isTriggerScheduler()) { diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 13f1490a2..ae0365019 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1214,9 +1214,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, // Process procedures per statement (unless profCFuncs), so we can split CFuncs within // procedures. Everything else is handled in one go bool suspendable = false; + bool needProcess = false; bool slow = m_slow; if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { suspendable = procp->isSuspendable(); + needProcess = procp->needProcess(); if (suspendable) slow = slow && !VN_IS(procp, Always); nodep = procp->stmtsp(); pushDeletep(procp); @@ -1241,6 +1243,7 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, const string name = cfuncName(modp, domainp, scopep, nodep); newFuncpr = new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""}; + if (needProcess) newFuncpr->setNeedProcess(); newFuncpr->isStatic(false); newFuncpr->isLoose(true); newFuncpr->slow(slow); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index fc26a192d..78f09d44b 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -247,6 +247,7 @@ 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(); diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index e0d3e908d..ec816bc70 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -155,6 +155,19 @@ TimingKit prepareTiming(AstNetlist* const netlistp) { std::vector m_writtenBySuspendable; // METHODS + // Add arguments to a resume() call based on arguments in the suspending call + void addResumePins(AstCMethodHard* const resumep, AstNodeExpr* pinsp) { + AstCExpr* const exprp = VN_CAST(pinsp, CExpr); + AstText* const textp = VN_CAST(exprp->exprsp(), Text); + if (textp) { + // The first argument, vlProcess, isn't used by any of resume() methods, skip it + if ((pinsp = VN_CAST(pinsp->nextp(), NodeExpr))) { + resumep->addPinsp(pinsp->cloneTree(false)); + } + } else { + resumep->addPinsp(pinsp->cloneTree(false)); + } + } // Create an active with a timing scheduler resume() call void createResumeActive(AstCAwait* const awaitp) { auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard); @@ -171,7 +184,8 @@ TimingKit prepareTiming(AstNetlist* const netlistp) { // The first pin is the commit boolean, the rest (if any) should be debug info // See V3Timing for details if (AstNode* const dbginfop = methodp->pinsp()->nextp()) { - resumep->addPinsp(static_cast(dbginfop)->cloneTree(false)); + if (methodp->pinsp()) + addResumePins(resumep, static_cast(dbginfop)); } } else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) { auto* const postp = resumep->cloneTree(false); @@ -262,6 +276,7 @@ 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 @@ -348,8 +363,9 @@ 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) { + if (m_beginHasAwaits || m_beginNeedProcess) { 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(); @@ -372,9 +388,19 @@ void transformForks(AstNetlist* const netlistp) { } // Put the begin's statements in the function, delete the begin newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); + if (m_beginNeedProcess) { + newfuncp->setNeedProcess(); + newfuncp->addStmtsp(new AstCStmt{nodep->fileline(), + "vlProcess->state(VlProcess::FINISHED);\n"}); + } + if (!m_beginHasAwaits) { + // co_return at the end (either that or a co_await is required in a coroutine + newfuncp->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"}); + } remapLocals(newfuncp, callp); } else { - // No awaits, just inline the forked process + // The begin has neither awaits nor a process::self call, just inline the + // statements nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext()); } VL_DO_DANGLING(nodep->deleteTree(), nodep); @@ -383,6 +409,10 @@ 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(AstNodeExpr*) override {} // Accelerate diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 95691ce4f..5f1e195ce 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1316,6 +1316,9 @@ private: } } + // Mark the fact that this function allocates std::process + if (nodep->isFromStd() && nodep->name() == "self") cfuncp->setNeedProcess(); + // Delete rest of cloned task and return new func VL_DO_DANGLING(pushDeletep(nodep), nodep); if (debug() >= 9) cfuncp->dumpTree("- userFunc: "); diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 6772f95f0..fa9305000 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -61,6 +61,16 @@ 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 +}; + // ###################################################################### // Detect nodes affected by timing @@ -94,8 +104,10 @@ private: // AstClass::user1() -> bool. Set true if the class // member cache has been // refreshed. - // Ast{NodeProcedure,CFunc,Begin}::user2() -> bool. Set true if process/task is - // suspendable + // Ast{NodeProcedure,CFunc,Begin}::user2() -> int. Set to >= T_SUSP if + // process/task suspendable + // and to T_PROC if it + // needs process metadata. // Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; @@ -113,15 +125,23 @@ private: if (!nodep->user3p()) nodep->user3p(new TimingDependencyVertex{&m_depGraph, nodep}); return nodep->user3u().to(); } - // Propagate suspendable flag to all nodes that depend on the given one - void propagateSuspendable(TimingDependencyVertex* const vxp) { + // 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); + return true; + } + return false; + } + // Propagate suspendable/needProcess flag to all nodes that depend on the given one + void propagateTimingFlags(TimingDependencyVertex* const vxp) { + auto* const parentp = vxp->nodep(); for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) { auto* const depVxp = static_cast(edgep->fromp()); AstNode* const depp = depVxp->nodep(); - if (!depp->user2()) { - depp->user2(true); - propagateSuspendable(depVxp); - } + if (setTimingFlag(depp, parentp->user2())) propagateTimingFlags(depVxp); } } @@ -142,6 +162,7 @@ private: m_procp = nodep; iterateChildren(nodep); TimingDependencyVertex* const vxp = getDependencyVertex(nodep); + if (nodep->needProcess()) nodep->user2(T_PROC); if (!m_classp) return; // If class method (possibly overrides another method) if (!m_classp->user1SetOnce()) m_classp->repairCache(); @@ -162,15 +183,13 @@ private: // the root of the inheritance hierarchy and check if the original method is // virtual or not. if (!cextp->classp()->user1SetOnce()) cextp->classp()->repairCache(); - if (AstCFunc* const overriddenp + if (auto* const overriddenp = VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) { - if (overriddenp->user2()) { // If it's suspendable - nodep->user2(true); // Then we are also suspendable - // As both are suspendable already, there is no need to add it as our - // dependency or self to its dependencies - } else { - // Make a dependency cycle, as being suspendable should propagate both - // up and down the inheritance tree + 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}; @@ -184,11 +203,8 @@ private: } } void visit(AstNodeCCall* nodep) override { - if (nodep->funcp()->user2()) { - m_procp->user2(true); - // Both the caller and the callee are suspendable, no need to make dependency edges - // between them - } else { + 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}; @@ -203,7 +219,7 @@ private: void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { v3Global.setUsesTiming(); - if (m_procp) m_procp->user2(true); + if (m_procp) m_procp->user2(T_SUSP); } iterateChildren(nodep); } @@ -218,7 +234,7 @@ public: m_depGraph.removeTransitiveEdges(); for (V3GraphVertex* vxp = m_depGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { TimingDependencyVertex* const depVxp = static_cast(vxp); - if (depVxp->nodep()->user2()) propagateSuspendable(depVxp); + if (depVxp->nodep()->user2()) propagateTimingFlags(depVxp); } if (dumpGraphLevel() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps"); } @@ -252,6 +268,7 @@ private: AstClass* m_classp = nullptr; // Current class AstScope* m_scopep = nullptr; // Current scope AstActive* m_activep = nullptr; // Current active + AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under double m_timescaleFactor = 1.0; // Factor to scale delays by // Unique names @@ -460,6 +477,14 @@ private: methodp->addPinsp(createEventDescription(sensesp)); addDebugInfo(methodp); } + // Adds process pointer to a hardcoded method call + 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}; + ap->dtypeSetVoid(); + methodp->addPinsp(ap); + } // Creates the fork handle type and returns it AstBasicDType* getCreateForkSyncDTypep() { if (m_forkDtp) return m_forkDtp; @@ -512,11 +537,13 @@ private: auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "init", new AstConst{flp, joinCount}}; initp->dtypeSetVoid(); + addProcessInfo(initp); forkp->addHereThisAsNext(initp->makeStmt()); // Await the join at the end auto* const joinp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"}; joinp->dtypeSetVoid(); + addProcessInfo(joinp); addDebugInfo(joinp); AstCAwait* const awaitp = new AstCAwait{flp, joinp}; awaitp->dtypeSetVoid(); @@ -543,13 +570,24 @@ private: m_activep = nullptr; } void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; iterateChildren(nodep); - if (nodep->user2()) nodep->setSuspendable(); + if (nodep->user2() >= T_SUSP) nodep->setSuspendable(); + if (nodep->user2() >= T_PROC) nodep->setNeedProcess(); + } + void visit(AstInitial* nodep) override { + visit(static_cast(nodep)); + if (nodep->needProcess() && !nodep->user1SetOnce()) { + nodep->addStmtsp( + new AstCStmt{nodep->fileline(), "vlProcess->state(VlProcess::FINISHED);\n"}); + } } void visit(AstAlways* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); if (!nodep->user2()) return; + if (nodep->user2() == T_PROC) nodep->setNeedProcess(); nodep->setSuspendable(); FileLine* const flp = nodep->fileline(); AstSenTree* const sensesp = m_activep->sensesp(); @@ -567,6 +605,8 @@ private: m_activep->addNextHere(activep); } void visit(AstCFunc* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; iterateChildren(nodep); if (!nodep->user2()) return; nodep->rtnType("VlCoroutine"); @@ -588,6 +628,7 @@ 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 @@ -627,6 +668,7 @@ private: auto* const delayMethodp = new AstCMethodHard{ flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep}; delayMethodp->dtypeSetVoid(); + addProcessInfo(delayMethodp); addDebugInfo(delayMethodp); // Create the co_await AstCAwait* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()}; @@ -666,6 +708,7 @@ private: flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE}, "evaluation"}; evalMethodp->dtypeSetVoid(); + addProcessInfo(evalMethodp); auto* const sensesp = nodep->sensesp(); addEventDebugInfo(evalMethodp, sensesp); // Create the co_await @@ -723,6 +766,7 @@ private: // If it should be committed immediately, pass true, otherwise false triggerMethodp->addPinsp(nodep->user2() ? new AstConst{flp, AstConst::BitTrue{}} : new AstConst{flp, AstConst::BitFalse{}}); + addProcessInfo(triggerMethodp); addEventDebugInfo(triggerMethodp, sensesp); // Create the co_await AstCAwait* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp}; @@ -858,6 +902,11 @@ private: } VL_DO_DANGLING(nodep->deleteTree(), nodep); } + void visit(AstBegin* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; + iterateChildren(nodep); + } void visit(AstFork* nodep) override { if (nodep->user1SetOnce()) return; v3Global.setUsesTiming(); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 95f3c3915..9ff2f067e 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -232,7 +232,7 @@ private: const AstWith* m_withp = nullptr; // Current 'with' statement const AstFunc* m_funcp = nullptr; // Current function const AstAttrOf* m_attrp = nullptr; // Current attribute - AstNodeModule* m_modp = nullptr; // Current module + AstPackage* m_pkgp = nullptr; // Current package const bool m_paramsOnly; // Computing parameter value; limit operation const bool m_doGenerate; // Do errors later inside generate statement int m_dtTables = 0; // Number of created data type tables @@ -2628,6 +2628,16 @@ private: } void visit(AstClass* nodep) override { if (nodep->didWidthAndSet()) return; + // If the package is std::process, set m_process type to VlProcessRef + if (m_pkgp && m_pkgp->name() == "std" && nodep->name() == "process") { + if (AstVar* const varp = VN_CAST(nodep->findMember("m_process"), Var)) { + AstBasicDType* const dtypep = new AstBasicDType{ + nodep->fileline(), VBasicDTypeKwd::PROCESS_REFERENCE, VSigning::UNSIGNED}; + v3Global.rootp()->typeTablep()->addTypesp(dtypep); + varp->getChildDTypep()->unlinkFrBack(); + varp->dtypep(dtypep); + } + } // Must do extends first, as we may in functions under this class // start following a tree of extends that takes us to other classes VL_RESTORER(m_classp); @@ -2636,6 +2646,11 @@ private: userIterateChildren(nodep, nullptr); // First size all members nodep->repairCache(); } + void visit(AstPackage* nodep) override { + VL_RESTORER(m_pkgp); + m_pkgp = nodep; + userIterateChildren(nodep, nullptr); + } void visit(AstThisRef* nodep) override { if (nodep->didWidthAndSet()) return; nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep())); @@ -2646,12 +2661,6 @@ private: // though causes problems with t_class_forward.v, so for now avoided // userIterateChildren(nodep->classp(), nullptr); } - void visit(AstNodeModule* nodep) override { - // Visitor does not include AstClass - specialized visitor above - VL_RESTORER(m_modp); - m_modp = nodep; - userIterateChildren(nodep, nullptr); - } void visit(AstClassOrPackageRef* nodep) override { if (nodep->didWidthAndSet()) return; userIterateChildren(nodep, nullptr); @@ -3491,7 +3500,7 @@ private: VL_DANGLING(index_exprp); // May have been edited return VN_AS(nodep->pinsp(), Arg)->exprp(); } - void methodCallWarnTiming(AstMethodCall* const nodep, const std::string& className) { + void methodCallWarnTiming(AstNodeFTaskRef* const nodep, const std::string& className) { if (v3Global.opt.timing().isSetFalse()) { nodep->v3warn(E_NOTIMING, className << "::" << nodep->name() << "() requires --timing"); @@ -3515,14 +3524,12 @@ private: if (classp->name() == "semaphore" || classp->name() == "process" || VString::startsWith(classp->name(), "mailbox")) { // Find the package the class is in - AstNode* pkgItemp = classp; - while (pkgItemp->backp() && pkgItemp->backp()->nextp() == pkgItemp) { - pkgItemp = pkgItemp->backp(); - } - AstPackage* const packagep = VN_CAST(pkgItemp->backp(), Package); + AstPackage* const packagep = getItemPackage(classp); // Check if it's std if (packagep && packagep->name() == "std") { - if (classp->name() == "semaphore" && nodep->name() == "get") { + if (classp->name() == "process") { + methodCallWarnTiming(nodep, "process"); + } else if (classp->name() == "semaphore" && nodep->name() == "get") { methodCallWarnTiming(nodep, "semaphore"); } else if (nodep->name() == "put" || nodep->name() == "get" || nodep->name() == "peek") { @@ -5323,6 +5330,7 @@ private: nodep->didWidth(true); return; } + if (m_pkgp && m_pkgp->name() == "std") nodep->isFromStd(true); if (nodep->classMethod() && nodep->name() == "rand_mode") { nodep->v3error("The 'rand_mode' method is built-in and cannot be overridden" " (IEEE 1800-2017 18.8)"); @@ -5405,9 +5413,25 @@ private: } } + AstPackage* getItemPackage(AstNode* pkgItemp) { + while (pkgItemp->backp() && pkgItemp->backp()->nextp() == pkgItemp) { + pkgItemp = pkgItemp->backp(); + } + return VN_CAST(pkgItemp->backp(), Package); + } void visit(AstFuncRef* nodep) override { visit(static_cast(nodep)); nodep->dtypeFrom(nodep->taskp()); + if (nodep->fileline()->timingOn()) { + AstNodeModule* const classp = nodep->classOrPackagep(); + if (nodep->name() == "self" && classp->name() == "process") { + // Find if package the class is in is std:: + AstPackage* const packagep = getItemPackage(classp); + if (packagep && packagep->name() == "std") { + methodCallWarnTiming(nodep, "process"); + } + } + } // if (debug()) nodep->dumpTree("- FuncOut: "); } // Returns true if dtypep0 and dtypep1 have same dimensions diff --git a/test_regress/t/t_process.out b/test_regress/t/t_process.out index ab04bc4e8..f0d3cbfc9 100644 --- a/test_regress/t/t_process.out +++ b/test_regress/t/t_process.out @@ -1,3 +1,2 @@ -[0] %Error: verilated_std.sv:154: Assertion failed in top.std.process.set_randstate: std::process::set_randstate() not supported -%Error: verilated_std.sv:154: Verilog $stop -Aborting... +'{m_process:process} +*-* All Finished *-* diff --git a/test_regress/t/t_process.pl b/test_regress/t/t_process.pl index 39ea187ff..b477231cf 100755 --- a/test_regress/t/t_process.pl +++ b/test_regress/t/t_process.pl @@ -10,14 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -compile( - ); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + v_flags2 => ["--timing"], + ); -execute( - fails => $Self->{vlt_all}, - expect_filename => $Self->{golden_filename}, - check_finished => !$Self->{vlt_all}, - ); + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} ok(1); 1; diff --git a/test_regress/t/t_process.v b/test_regress/t/t_process.v index 0d495ab17..9d4f3fbb8 100644 --- a/test_regress/t/t_process.v +++ b/test_regress/t/t_process.v @@ -38,6 +38,8 @@ module t(/*AUTOARG*/); p.srandom(0); p.set_randstate(p.get_randstate()); + $display("%p", p); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_process_bad.pl b/test_regress/t/t_process_bad.pl index dd3bcbbf3..c87b2fc6f 100755 --- a/test_regress/t/t_process_bad.pl +++ b/test_regress/t/t_process_bad.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - verilator_flags2 => ["--xml-only"], + verilator_flags2 => ["--xml-only", "--timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_process_finished.pl b/test_regress/t/t_process_finished.pl new file mode 100755 index 000000000..1db13024b --- /dev/null +++ b/test_regress/t/t_process_finished.pl @@ -0,0 +1,27 @@ +#!/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); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_process_finished.v b/test_regress/t/t_process_finished.v new file mode 100644 index 000000000..df6f6cc28 --- /dev/null +++ b/test_regress/t/t_process_finished.v @@ -0,0 +1,24 @@ +// 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 (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + process p; + + initial begin + p = process::self(); + end + + always @(posedge clk) begin + if (p.status() != process::FINISHED) + $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_process_fork.out b/test_regress/t/t_process_fork.out new file mode 100644 index 000000000..8b4a1a959 --- /dev/null +++ b/test_regress/t/t_process_fork.out @@ -0,0 +1,11 @@ +job started +job started +job started +job started +job started +job started +job started +job started +all jobs started +all jobs finished +*-* All Finished *-* diff --git a/test_regress/t/t_process_fork.pl b/test_regress/t/t_process_fork.pl new file mode 100755 index 000000000..1af68b062 --- /dev/null +++ b/test_regress/t/t_process_fork.pl @@ -0,0 +1,28 @@ +#!/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); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_process_fork.v b/test_regress/t/t_process_fork.v new file mode 100644 index 000000000..1ea1c477e --- /dev/null +++ b/test_regress/t/t_process_fork.v @@ -0,0 +1,30 @@ +// 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; + process job[] = new [8]; + bit is_alloc = 0; + + initial begin + foreach (job[j]) fork + begin + $write("job started\n"); + job[j] = process::self(); + end + join_none + foreach (job[j]) begin + is_alloc = !!job[j]; + wait (is_alloc); + end + $write("all jobs started\n"); + foreach (job[j]) begin + job[j].await(); + end + $write("all jobs finished\n"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_process_kill.pl b/test_regress/t/t_process_kill.pl new file mode 100755 index 000000000..1db13024b --- /dev/null +++ b/test_regress/t/t_process_kill.pl @@ -0,0 +1,27 @@ +#!/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); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_process_kill.v b/test_regress/t/t_process_kill.v new file mode 100644 index 000000000..8c0252939 --- /dev/null +++ b/test_regress/t/t_process_kill.v @@ -0,0 +1,31 @@ +// 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 (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + process p; + bit s = 0; + + initial begin + wait (s); + p.kill(); + p.await(); + $write("*-* All Finished *-*\n"); + $finish; + end + + always @(posedge clk) begin + if (!p) begin + p = process::self(); + s = 1; + end else begin + $stop; + end + end +endmodule diff --git a/test_regress/t/t_process_notiming.out b/test_regress/t/t_process_notiming.out new file mode 100644 index 000000000..833040f13 --- /dev/null +++ b/test_regress/t/t_process_notiming.out @@ -0,0 +1,54 @@ +%Error-NOTIMING: t/t_process.v:26:20: process::self() requires --timing + : ... In instance t + 26 | p = process::self(); + | ^~~~ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_process.v:27:13: process::status() requires --timing + : ... In instance t + 27 | if (p.status() != process::RUNNING) $stop; + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:28:13: process::status() requires --timing + : ... In instance t + 28 | if (p.status() == process::WAITING) $stop; + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:29:13: process::status() requires --timing + : ... In instance t + 29 | if (p.status() == process::SUSPENDED) $stop; + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:30:13: process::status() requires --timing + : ... In instance t + 30 | if (p.status() == process::KILLED) $stop; + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:31:13: process::status() requires --timing + : ... In instance t + 31 | if (p.status() == process::FINISHED) $stop; + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:33:16: process::kill() requires --timing + : ... In instance t + 33 | if (0) p.kill(); + | ^~~~ +%Error-NOTIMING: t/t_process.v:34:16: process::await() requires --timing + : ... In instance t + 34 | if (0) p.await(); + | ^~~~~ +%Error-NOTIMING: t/t_process.v:35:16: process::suspend() requires --timing + : ... In instance t + 35 | if (0) p.suspend(); + | ^~~~~~~ +%Error-NOTIMING: t/t_process.v:36:16: process::resume() requires --timing + : ... In instance t + 36 | if (0) p.resume(); + | ^~~~~~ +%Error-NOTIMING: t/t_process.v:38:9: process::srandom() requires --timing + : ... In instance t + 38 | p.srandom(0); + | ^~~~~~~ +%Error-NOTIMING: t/t_process.v:39:25: process::get_randstate() requires --timing + : ... In instance t + 39 | p.set_randstate(p.get_randstate()); + | ^~~~~~~~~~~~~ +%Error-NOTIMING: t/t_process.v:39:9: process::set_randstate() requires --timing + : ... In instance t + 39 | p.set_randstate(p.get_randstate()); + | ^~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_process_notiming.pl b/test_regress/t/t_process_notiming.pl new file mode 100755 index 000000000..9a1abce98 --- /dev/null +++ b/test_regress/t/t_process_notiming.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 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); + +top_filename("t/t_process.v"); + +compile( + expect_filename => $Self->{golden_filename}, + v_flags2 => ["+define+T_PROCESS+std::process", "--no-timing"], + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_process_rand.pl b/test_regress/t/t_process_rand.pl new file mode 100755 index 000000000..95e68c03f --- /dev/null +++ b/test_regress/t/t_process_rand.pl @@ -0,0 +1,27 @@ +#!/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); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + v_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_process_rand.v b/test_regress/t/t_process_rand.v new file mode 100644 index 000000000..20978fc04 --- /dev/null +++ b/test_regress/t/t_process_rand.v @@ -0,0 +1,52 @@ +// 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; + process p; + + integer seed; + string state; + int a; + int b; + + initial begin + p = process::self(); + + // Test setting RNG state with state string + state = p.get_randstate(); + p.set_randstate(state); + a = $random; + p.set_randstate(state); + b = $random; + $display("a=%d, b=%d", a, b); + if (a != b) $stop; + + // Test the same with $urandom + state = p.get_randstate(); + p.set_randstate(state); + a = $urandom; + p.set_randstate(state); + b = $urandom; + $display("a=%d, b=%d", a, b); + if (a != b) $stop; + + // Test if the results repeat after the state is reset + state = p.get_randstate(); + for (int i = 0; i < 10; i++) + $random; + a = $random; + // Now reset the state and take 11th result again + p.set_randstate(state); + for (int i = 0; i < 10; i++) + $random; + b = $random; + $display("a=%d, b=%d", a, b); + if (a != b) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_process_std.pl b/test_regress/t/t_process_std.pl index 64cabbccb..d270c22a7 100755 --- a/test_regress/t/t_process_std.pl +++ b/test_regress/t/t_process_std.pl @@ -12,15 +12,18 @@ scenarios(simulator => 1); top_filename("t/t_process.v"); -compile( - v_flags2 => ["+define+T_PROCESS+std::process"], - ); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + v_flags2 => ["+define+T_PROCESS+std::process", "--timing"], + ); -execute( - check_finished => !$Self->{vlt_all}, - fails => $Self->{vlt_all}, - expect_filename => $Self->{golden_filename}, - ); + execute( + check_finished => 1, + ) if !$Self->{vlt_all}; +} ok(1); 1;