diff --git a/include/verilated.cpp b/include/verilated.cpp index fd86e9d6b..35c965dff 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -160,6 +160,7 @@ void vl_finish(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE #ifndef VL_USER_STOP ///< Define this to override the vl_stop function void vl_stop(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE { // $stop or $fatal reporting; would break current API to add param as to which + if (Verilated::threadContextp()->gotFinish()) return; const char* const msg = "Verilog $stop"; Verilated::threadContextp()->gotError(true); Verilated::threadContextp()->gotFinish(true); diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index b55086416..51dd88b9a 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -94,7 +94,7 @@ uint64_t VlDelayScheduler::nextTimeSlot() const { #ifdef VL_DEBUG void VlDelayScheduler::dump() const { - if (m_queue.empty()) { + if (m_queue.empty() && m_zeroDelayed.empty()) { VL_DBG_MSGF(" No delayed processes:\n"); } else { VL_DBG_MSGF(" Delayed processes:\n"); diff --git a/include/verilated_timing.h b/include/verilated_timing.h index faceb8926..a657ac458 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -28,6 +28,7 @@ #include "verilated.h" +#include #include // clang-format off @@ -209,7 +210,8 @@ public: bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { - if (phase == VlDelayPhase::ACTIVE) { + // Both active delays and fork..join_none #0 are resumed out of the time queue. + if (phase != VlDelayPhase::INACTIVE) { queue.emplace(delay, VlCoroutineHandle{coro, process, fileline}); } else { queueZeroDelay.emplace_back(VlCoroutineHandle{coro, process, fileline}); @@ -218,7 +220,14 @@ public: void await_resume() const {} }; - const VlDelayPhase phase = (delay == 0) ? VlDelayPhase::INACTIVE : VlDelayPhase::ACTIVE; + VlDelayPhase phase; + if (delay != 0) { + // UINT64_MAX is a sentinel for synthetic fork..join_none delays. + if (delay == std::numeric_limits::max()) delay = 0; + phase = VlDelayPhase::ACTIVE; + } else { + phase = VlDelayPhase::INACTIVE; + } return Awaitable{process, m_queue, m_zeroDelayed, m_context.time() + delay, phase, VlFileLineDebug{filename, lineno}}; diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index e0ca96e04..280c04323 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1421,7 +1421,6 @@ class AstFork final : public AstNodeBlock { // be executed sequentially within each fork branch. // // @astgen op3 := forksp : List[AstBegin] - // @astgen op4 := parentProcessp : Optional[AstVarRef] const VJoinType m_joinType; // Join keyword type public: AstFork(FileLine* fl, VJoinType joinType, const string& name = "") diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 6b810fffb..a6de109bb 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -47,6 +47,7 @@ #include "V3AstNodeExpr.h" #include "V3MemberMap.h" +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -90,7 +91,7 @@ public: m_instance.m_handlep = new AstVar{m_procp->fileline(), VVarType::BLOCKTEMP, generateDynScopeHandleName(m_procp), m_instance.m_refDTypep}; - m_instance.m_handlep->funcLocal(true); + m_instance.m_handlep->funcLocal(false); m_instance.m_handlep->lifetime(VLifetime::AUTOMATIC_EXPLICIT); UINFO(9, "new dynscope var " << m_instance.m_handlep); @@ -336,12 +337,14 @@ class DynScopeVisitor final : public VNVisitor { } bool needsDynScope(const AstVarRef* refp) const { + const AstVar* const varp = refp->varp(); + const bool localLifetime = varp->isFuncLocal() || varp->lifetime().isAutomatic(); return // Can this variable escape the scope - ((m_forkDepth > refp->varp()->user1()) && refp->varp()->isFuncLocal()) + ((m_forkDepth > varp->user1()) && localLifetime) && ( // Is it mutated - (refp->varp()->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW()) + (varp->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW()) // Or is it after a timing-control event || m_afterTimingControl); } @@ -527,11 +530,6 @@ class ForkVisitor final : public VNVisitor { AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables AstArg* m_capturedArgsp = nullptr; // References to captured variables (as args) - // STATE - across all visitors - AstClass* m_processClassp = nullptr; - AstFunc* m_statusMethodp = nullptr; - VMemberMap m_memberMap; // for lookup of process class methods - // METHODS AstVar* capture(AstVarRef* refp) { AstVar* varp = nullptr; @@ -585,19 +583,6 @@ class ForkVisitor final : public VNVisitor { return true; } - AstClass* getProcessClassp() { - if (!m_processClassp) - m_processClassp - = VN_AS(m_memberMap.findMember(v3Global.rootp()->stdPackagep(), "process"), Class); - return m_processClassp; - } - - AstFunc* getStatusmethodp() { - if (m_statusMethodp == nullptr) - m_statusMethodp = VN_AS(m_memberMap.findMember(getProcessClassp(), "status"), Func); - return m_statusMethodp; - } - // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -624,24 +609,16 @@ class ForkVisitor final : public VNVisitor { if (nodep->joinType().joinNone()) { UINFO(9, "Visiting fork..join_none " << nodep); FileLine* fl = nodep->fileline(); - AstVarRef* forkParentrefp = nodep->parentProcessp(); - - if (forkParentrefp) { // Forks created by V3Fork will not have this - for (AstBegin *itemp = nodep->forksp(), *nextp; itemp; itemp = nextp) { - nextp = VN_AS(itemp->nextp(), Begin); - if (!itemp->stmtsp()) continue; - AstMethodCall* const statusCallp = new AstMethodCall{ - fl, forkParentrefp->cloneTree(false), "status", nullptr}; - statusCallp->taskp(getStatusmethodp()); - statusCallp->classOrPackagep(getProcessClassp()); - statusCallp->dtypep(getStatusmethodp()->dtypep()); - AstNeq* const condp - = new AstNeq{fl, statusCallp, - new AstConst{fl, AstConst::WidthedValue{}, - getStatusmethodp()->dtypep()->width(), 1}}; - AstWait* const waitStmt = new AstWait{fl, condp, nullptr}; - itemp->stmtsp()->addHereThisAsNext(waitStmt); - } + // We use a sentinel value of UINT64_MAX to mark this delay so that it goes to the + // ACTIVE region with a delay value of 0. + for (AstBegin *itemp = nodep->forksp(), *nextp; itemp; itemp = nextp) { + nextp = VN_AS(itemp->nextp(), Begin); + if (!itemp->stmtsp()) continue; + AstDelay* const delayp = new AstDelay{ + fl, + new AstConst{fl, AstConst::Unsized64{}, std::numeric_limits::max()}, + false}; + itemp->stmtsp()->addHereThisAsNext(delayp); } } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 4c5009f9a..e831c9a76 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -653,29 +653,6 @@ private: return findp; } - VSymEnt* findForkParentAlias(VSymEnt* symp, const string& ident) { - static const string suffix = "__VgetForkParent"; - VSymEnt* const wrapperp = symp->findIdFlat(ident + suffix); - if (!wrapperp) return nullptr; - if (!VN_IS(wrapperp->nodep(), Begin)) return nullptr; - if (VSymEnt* const forkSymp = wrapperp->findIdFlat(ident)) { - if (VN_IS(forkSymp->nodep(), Fork)) return forkSymp; - } - return nullptr; - } - - VSymEnt* unwrapForkParent(VSymEnt* symp, const string& ident) { - static const string suffix = "__VgetForkParent"; - if (AstBegin* const beginp = VN_CAST(symp->nodep(), Begin)) { - if (VString::endsWith(beginp->name(), suffix)) { - if (VSymEnt* const forkSymp = symp->findIdFlat(ident)) { - if (VN_IS(forkSymp->nodep(), Fork)) return forkSymp; - } - } - } - return symp; - } - VSymEnt* findWithAltFlat(VSymEnt* symp, const string& name, const string& altname) { VSymEnt* findp = symp->findIdFlat(name); if (findp) return findp; @@ -726,9 +703,9 @@ public: const AstCellInline* inlinep = lookupSymp ? VN_CAST(lookupSymp->nodep(), CellInline) : nullptr; // Replicated below - VSymEnt* findSymp = findWithAltFallback(lookupSymp, ident, altIdent); - if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident); - if (findSymp) lookupSymp = unwrapForkParent(findSymp, ident); + if (VSymEnt* const findSymp = findWithAltFallback(lookupSymp, ident, altIdent)) { + lookupSymp = findSymp; + } // Check this module - cur modname else if ((cellp && cellp->modp()->origName() == ident) @@ -776,9 +753,8 @@ public: } } else { // Searching for middle path component, must be a cell or interface port VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent); - if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident); if (findSymp) { - lookupSymp = unwrapForkParent(findSymp, ident); + lookupSymp = findSymp; } else { // Try prefixed lookup for interface ports accessed through hierarchy // (e.g., slave_inst.bus.data where bus is an interface port) diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index d79275375..244ed53e4 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -23,7 +23,6 @@ #include "V3LinkParse.h" #include "V3Control.h" -#include "V3MemberMap.h" #include "V3Stats.h" #include @@ -47,7 +46,6 @@ class LinkParseVisitor final : public VNVisitor { // STATE - across all visitors std::unordered_set m_filelines; // Filelines that have been seen - VMemberMap m_memberMap; // for lookup of process class methods // STATE - for current visit position (use VL_RESTORER) // If set, move AstVar->valuep() initial values to this module @@ -75,9 +73,6 @@ class LinkParseVisitor final : public VNVisitor { // STATE - Statistic tracking VDouble0 m_statModules; // Number of modules seen - bool m_unprotectedStdProcess - = false; // Set when std::process internals were unprotected, we only need to do this once - // METHODS void cleanFileline(AstNode* nodep) { if (nodep->user2SetOnce()) return; // Process once @@ -111,21 +106,6 @@ class LinkParseVisitor final : public VNVisitor { return ""; } - void unprotectStdProcessHandle() { - if (m_unprotectedStdProcess) return; - m_unprotectedStdProcess = true; - if (!v3Global.opt.protectIds()) return; - if (AstPackage* const stdp = v3Global.rootp()->stdPackagep()) { - if (AstClass* const processp - = VN_CAST(m_memberMap.findMember(stdp, "process"), Class)) { - if (AstVar* const handlep - = VN_CAST(m_memberMap.findMember(processp, "m_process"), Var)) { - handlep->protect(false); - } - } - } - } - void visitIterateNodeDType(AstNodeDType* nodep) { if (nodep->user1SetOnce()) return; // Process only once. cleanFileline(nodep); @@ -201,38 +181,6 @@ class LinkParseVisitor final : public VNVisitor { << nodep->warnContextSecondary()); } - void addForkParentProcess(AstFork* forkp) { - FileLine* const fl = forkp->fileline(); - - const std::string parentName = "__VforkParent"; - AstRefDType* const dtypep = new AstRefDType{fl, "process"}; - AstVar* const parentVar - = new AstVar{fl, VVarType::BLOCKTEMP, parentName, VFlagChildDType{}, dtypep}; - parentVar->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - - AstParseRef* const lhsp = new AstParseRef{fl, parentName, nullptr, nullptr}; - AstClassOrPackageRef* const processRefp - = new AstClassOrPackageRef{fl, "process", nullptr, nullptr}; - AstParseRef* const selfRefp = new AstParseRef{fl, "self", nullptr, nullptr}; - AstDot* const processSelfp = new AstDot{fl, true, processRefp, selfRefp}; - AstMethodCall* const callp = new AstMethodCall{fl, processSelfp, "self", nullptr}; - AstAssign* const initp = new AstAssign{fl, lhsp, callp}; - - AstVarRef* const parentRefp = new AstVarRef{fl, parentVar, VAccess::READ}; - forkp->parentProcessp(parentRefp); - - VNRelinker relinker; - forkp->unlinkFrBack(&relinker); - - parentVar->addNextHere(initp); - initp->addNextHere(forkp); - - AstBegin* const beginp = new AstBegin{ - fl, forkp->name() == "" ? "" : forkp->name() + "__VgetForkParent", parentVar, true}; - - relinker.relink(beginp); - } - // VISITORS void visit(AstNodeFTask* nodep) override { if (nodep->user1SetOnce()) return; // Process only once. @@ -319,6 +267,10 @@ class LinkParseVisitor final : public VNVisitor { cleanFileline(nodep); UINFO(9, "VAR " << nodep); if (nodep->valuep()) nodep->hasUserInit(true); + if (m_insideLoop && nodep->lifetime().isNone() && nodep->varType() == VVarType::VAR + && !nodep->direction().isAny()) { + nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + } if (nodep->lifetime().isStatic() && m_insideLoop && nodep->valuep()) { nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT); nodep->v3warn(STATICVAR, "Static variable with assignment declaration declared in a " @@ -854,11 +806,7 @@ class LinkParseVisitor final : public VNVisitor { } cleanFileline(nodep); iterateAndNextNull(nodep->stmtsp()); - if (AstFork* const forkp = VN_CAST(nodep, Fork)) { - iterateAndNextNull(forkp->forksp()); - if (!forkp->parentProcessp() && forkp->joinType().joinNone() && forkp->forksp()) - addForkParentProcess(forkp); - } + if (AstFork* const forkp = VN_CAST(nodep, Fork)) iterateAndNextNull(forkp->forksp()); } void visit(AstCase* nodep) override { V3Control::applyCase(nodep); @@ -1066,10 +1014,7 @@ class LinkParseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit LinkParseVisitor(AstNetlist* rootp) { - unprotectStdProcessHandle(); - iterate(rootp); - } + explicit LinkParseVisitor(AstNetlist* rootp) { iterate(rootp); } ~LinkParseVisitor() override { V3Stats::addStatSum(V3Stats::STAT_SOURCE_MODULES, m_statModules); } diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index aef685da6..373402133 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -364,12 +364,4 @@ public: } return resp; } - - // Wrap fork statements in AstBegin, ensure fork...join_none have process - static AstNodeStmt* wrapFork(V3ParseImp* parsep, AstFork* forkp, AstNodeStmt* stmtsp) { - if (forkp->joinType() == VJoinType::JOIN_NONE && stmtsp) - parsep->importIfInStd(forkp->fileline(), "process", true); - forkp->addForksp(wrapInBegin(stmtsp)); - return forkp; - } }; diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 9bc7bebe2..7d50e2a17 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -210,9 +210,11 @@ private: // Find all var->varscope mappings, for later cleanup for (AstNode* stmtp = nodep->varsp(); stmtp; stmtp = stmtp->nextp()) { if (AstVarScope* const vscp = VN_CAST(stmtp, VarScope)) { - if (vscp->varp()->isFuncLocal() || vscp->varp()->isUsedLoopIdx()) { + AstVar* const varp = vscp->varp(); + if (varp->isFuncLocal() || varp->isUsedLoopIdx() + || varp->lifetime().isAutomatic()) { UINFO(9, " funcvsc " << vscp); - m_varToScopeMap.emplace(std::make_pair(nodep, vscp->varp()), vscp); + m_varToScopeMap.emplace(std::make_pair(nodep, varp), vscp); } } } diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index b3900d6fb..b723cbcb9 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -74,6 +74,7 @@ #include "V3Stats.h" #include "V3UniqueNames.h" +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -627,9 +628,10 @@ class TimingControlVisitor final : public VNVisitor { // Returns true if the given trigger expression needs a destructive post update after trigger // evaluation. Currently this only applies to named events. bool destructivePostUpdate(AstNode* const exprp) const { - return exprp->exists([](const AstNodeVarRef* const refp) { - AstBasicDType* const dtypep = refp->dtypep()->basicp(); - return dtypep && dtypep->isEvent(); + return exprp->exists([](const AstNode* const nodep) { + const AstNodeDType* const dtypep = nodep->dtypep(); + const AstBasicDType* const basicp = dtypep ? dtypep->skipRefp()->basicp() : nullptr; + return basicp && basicp->isEvent(); }); } // Creates a trigger scheduler variable @@ -933,21 +935,28 @@ class TimingControlVisitor final : public VNVisitor { valuep = new AstConst{flp, AstConst::Unsized64{}, 1}; valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); } else { - // Scale the delay - const double timescaleFactorD = calculateTimescaleFactor(nodep, nodep->timeunit()); - if (valuep->dtypep()->skipRefp()->isDouble()) { - AstConst* const tsfp = new AstConst{flp, AstConst::RealDouble{}, timescaleFactorD}; - valuep = new AstMulD{flp, valuep, tsfp}; - valuep = new AstRToIRoundS{flp, valuep}; - valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); - } else { - valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); - const uint64_t timescaleFactorU = static_cast(timescaleFactorD); - AstConst* const tsfp = new AstConst{flp, AstConst::Unsized64{}, timescaleFactorU}; - valuep = new AstMul{flp, valuep, tsfp}; + AstConst* const constp = VN_CAST(valuep, Const); + const bool isForkSentinel + = constp && (constp->toUQuad() == std::numeric_limits::max()); + if (!isForkSentinel && (!constp || !constp->isZero())) { + // Scale the delay + const double timescaleFactorD = calculateTimescaleFactor(nodep, nodep->timeunit()); + if (valuep->dtypep()->skipRefp()->isDouble()) { + AstConst* const tsfp + = new AstConst{flp, AstConst::RealDouble{}, timescaleFactorD}; + valuep = new AstMulD{flp, valuep, tsfp}; + valuep = new AstRToIRoundS{flp, valuep}; + valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); + } else { + valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); + const uint64_t timescaleFactorU = static_cast(timescaleFactorD); + AstConst* const tsfp + = new AstConst{flp, AstConst::Unsized64{}, timescaleFactorU}; + valuep = new AstMul{flp, valuep, tsfp}; + } + // Simplify + valuep = V3Const::constifyEdit(valuep); } - // Simplify - valuep = V3Const::constifyEdit(valuep); } // Statistics diff --git a/src/verilog.y b/src/verilog.y index ce5f4e582..e44fa52d5 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3493,23 +3493,23 @@ par_blockJoin: | yJOIN_NONE { $$ = VJoinType::JOIN_NONE; } ; -par_block: // ==IEEE: par_block +par_block: // ==IEEE: par_block yFORK startLabelE blockDeclListE stmtListE par_blockJoin endLabelE { - AstFork* const forkp = new AstFork{$1, $5, $2 ? *$2 : ""}; - GRAMMARP->endLabel($6, forkp, $6); - forkp->addDeclsp($3); - $$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $4); + $$ = new AstFork{$1, $5, $2 ? *$2 : ""}; + GRAMMARP->endLabel($6, $$, $6); + $$->addDeclsp($3); + $$->addForksp(V3ParseGrammar::wrapInBegin($4)); } ; -par_blockPreId: // ==IEEE: par_block but called with leading ID +par_blockPreId: // ==IEEE: par_block but called with leading ID id yP_COLON__FORK yFORK blockDeclListE stmtListE par_blockJoin endLabelE { - AstFork* const forkp = new AstFork{$3, $6, *$1}; - GRAMMARP->endLabel($7, forkp, $7); - forkp->addDeclsp($4); - $$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $5); + $$ = new AstFork{$3, $6, *$1}; + GRAMMARP->endLabel($7, $$, $7); + $$->addDeclsp($4); + $$->addForksp(V3ParseGrammar::wrapInBegin($5)); } ; diff --git a/test_regress/t/t_fork_join_none_capture.py b/test_regress/t/t_fork_join_none_capture.py new file mode 100755 index 000000000..eb19ddb94 --- /dev/null +++ b/test_regress/t/t_fork_join_none_capture.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_join_none_capture.v b/test_regress/t/t_fork_join_none_capture.v new file mode 100644 index 000000000..471424db4 --- /dev/null +++ b/test_regress/t/t_fork_join_none_capture.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module test; + mailbox #(int) mbox; + initial begin + mbox = new(); + mbox.put(10); + mbox.put(30); + + repeat(2) begin + int item; + mbox.get(item); + fork + begin + $display("got", item); + if(item==10) + $finish; + end + join_none + end + + #0; + $stop; + end +endmodule diff --git a/test_regress/t/t_fork_join_none_inactive.out b/test_regress/t/t_fork_join_none_inactive.out new file mode 100644 index 000000000..536aaaf4e --- /dev/null +++ b/test_regress/t/t_fork_join_none_inactive.out @@ -0,0 +1,3 @@ +This should be first +This should be second +This should be last diff --git a/test_regress/t/t_fork_join_none_inactive.py b/test_regress/t/t_fork_join_none_inactive.py new file mode 100755 index 000000000..25cf19acc --- /dev/null +++ b/test_regress/t/t_fork_join_none_inactive.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_fork_join_none_inactive.v b/test_regress/t/t_fork_join_none_inactive.v new file mode 100644 index 000000000..5fd2dcbbd --- /dev/null +++ b/test_regress/t/t_fork_join_none_inactive.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial fork + #0 $write("This should be last\n"); + begin + fork $write("This should be second\n"); join_none + $write("This should be first\n"); + end + join_none +endmodule diff --git a/test_regress/t/t_fork_join_none_nested_triggered.py b/test_regress/t/t_fork_join_none_nested_triggered.py new file mode 100755 index 000000000..eb19ddb94 --- /dev/null +++ b/test_regress/t/t_fork_join_none_nested_triggered.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_join_none_nested_triggered.v b/test_regress/t/t_fork_join_none_nested_triggered.v new file mode 100644 index 000000000..0f209c5e5 --- /dev/null +++ b/test_regress/t/t_fork_join_none_nested_triggered.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, 2026 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module test; + mailbox #(int) mbox; + initial begin + mbox = new(); + + fork + repeat(2) begin + int val; + mbox.get(val); + fork + fork + begin + $finish; + end + join_none + join_none + end + join_none + + mbox.put(1); + #1; + $stop; + end +endmodule diff --git a/test_regress/t/t_fork_join_none_waiters.py b/test_regress/t/t_fork_join_none_waiters.py new file mode 100755 index 000000000..eb19ddb94 --- /dev/null +++ b/test_regress/t/t_fork_join_none_waiters.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_join_none_waiters.v b/test_regress/t/t_fork_join_none_waiters.v new file mode 100644 index 000000000..ed0533c1d --- /dev/null +++ b/test_regress/t/t_fork_join_none_waiters.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class events_holder; + event ev; +endclass + +module test; + events_holder m_events[int]; + int waiters_done = 0; + + initial begin + m_events[0] = new; + fork + begin + @(m_events[0].ev); + waiters_done++; + end + begin + @(m_events[0].ev); + waiters_done++; + end + join_none + #1; + ->m_events[0].ev; + + #1; + if (waiters_done == 2) $finish; + end + + initial #10000ns $stop; +endmodule diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index c06e2c359..66c5f4f0e 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -2,10 +2,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___ctor_var_reset -V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass__Vclpkg___ctor_var_reset -V{t#,#}+ Vt_timing_debug2___024unit___ctor_var_reset --V{t#,#}+ Vt_timing_debug2_std___ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t___ctor_var_reset --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg___ctor_var_reset --V{t#,#}+ Vt_timing_debug2_std__03a__03asemaphore__Vclpkg___ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass__Vclpkg___ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass__Vclpkg___ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10__Vclpkg___ctor_var_reset @@ -1342,28 +1339,19 @@ -V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_sth_else -V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_delay -V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_sth_else --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg::__VnoInFunc_self --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::new --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::_ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__Vtiming__6____Vfork_1__0 --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status --V{t#,#} Suspending process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_0__25____VforkParent.(t.__Vtask_status__26__Vfuncout); , t.__Vtask_status__26__Vfuncout); )) at t/t_timing_class.v:224 -V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:76 -V{t#,#} Process forked at t/t_timing_class.v:76 finished -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:131 -V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::__VnoInFunc_flip +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224 -V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers_vec__act -V{t#,#} Suspended processes waiting for dynamic trigger evaluation: -V{t#,#} - Process waiting at t/t_timing_class.v:75 --V{t#,#} Suspended processes waiting for dynamic trigger evaluation: --V{t#,#} - Process waiting at t/t_timing_class.v:224 -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:75 -V{t#,#} Process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:75 awaiting resumption --V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224 --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status --V{t#,#} Process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_0__25____VforkParent.(t.__Vtask_status__26__Vfuncout); , t.__Vtask_status__26__Vfuncout); )) at t/t_timing_class.v:224 awaiting resumption -V{t#,#}+ Vt_timing_debug2___024root___timing_ready -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act @@ -1376,11 +1364,9 @@ -V{t#,#} Resuming processes waiting for @([event] t.ec.e) -V{t#,#} Resuming processes: -V{t#,#} - Process waiting at t/t_timing_class.v:75 --V{t#,#} - Process waiting at t/t_timing_class.v:224 -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:75 -V{t#,#} Process forked at t/t_timing_class.v:75 finished -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:74 --V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224 -V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers_vec__act -V{t#,#} No suspended processes waiting for dynamic trigger evaluation @@ -1491,33 +1477,9 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:136 -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:190 --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg::__VnoInFunc_self --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::new --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::_ctor_var_reset -V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__Vtiming__6____Vfork_2__0 --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status --V{t#,#} Suspending process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_1__29____VforkParent.(t.__Vtask_status__30__Vfuncout); , t.__Vtask_status__30__Vfuncout); )) at t/t_timing_class.v:229 -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:131 -V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::__VnoInFunc_flip --V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act --V{t#,#}+ Vt_timing_debug2___024root___eval_triggers_vec__act --V{t#,#} Suspended processes waiting for dynamic trigger evaluation: --V{t#,#} - Process waiting at t/t_timing_class.v:229 --V{t#,#} Resuming: Process waiting at t/t_timing_class.v:229 --V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status --V{t#,#} Process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_1__29____VforkParent.(t.__Vtask_status__30__Vfuncout); , t.__Vtask_status__30__Vfuncout); )) at t/t_timing_class.v:229 awaiting resumption --V{t#,#}+ Vt_timing_debug2___024root___timing_ready --V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec --V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) --V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec --V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} No process to resume waiting for @([event] t.ec.e) --V{t#,#} Resuming processes waiting for @([event] t.ec.e) --V{t#,#} Resuming processes: --V{t#,#} - Process waiting at t/t_timing_class.v:229 -V{t#,#} Resuming: Process waiting at t/t_timing_class.v:229 -V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign -V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act diff --git a/test_regress/t/t_timing_fork_comb.py b/test_regress/t/t_timing_fork_comb.py index 7ded63f3a..45bd2d5a7 100755 --- a/test_regress/t/t_timing_fork_comb.py +++ b/test_regress/t/t_timing_fork_comb.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary"]) +test.compile(verilator_flags2=["--binary -Wno-UNOPTFLAT"]) test.execute() diff --git a/test_regress/t/t_timing_fork_comb_bad.py b/test_regress/t/t_timing_fork_comb_bad.py new file mode 100755 index 000000000..1b5d6f77c --- /dev/null +++ b/test_regress/t/t_timing_fork_comb_bad.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_timing_fork_comb.v" + +# Should convert the first always into combo and detect cycle +test.lint(fails=True, verilator_flags2=["--timing"]) + +test.file_grep( + test.compile_log_filename, + r'%Warning-UNOPTFLAT: t/t_timing_fork_comb.v:\d+:\d+: Signal unoptimizable: Circular combinational logic:' +) + +test.passes()