diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 8fef497de..f1f4f66d5 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1418,9 +1418,10 @@ class AstFork final : public AstNodeBlock { // spawned. This is necessary to implement things like local variable // initializers properly. The parallel statements inside the fork must all // be AstBegin, as lowering stages will introduce additional statements to - // be executed sequentially within eaach fork branch. + // 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/V3EmitCFunc.h b/src/V3EmitCFunc.h index ade7cf0e2..da2ba68ec 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1251,6 +1251,8 @@ public: puts(");\n"); } void visit(AstFinish* nodep) override { + // Disable all the forks so they don't operate after simulation is finished. + if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n"); putns(nodep, "VL_FINISH_MT("); putsQuoted(protect(nodep->fileline()->filename())); puts(", "); @@ -1258,6 +1260,8 @@ public: puts(", \"\");\n"); } void visit(AstFinishFork* nodep) override { + // Disable all the forks so they don't operate after simulation is finished. + if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n"); putns(nodep, "VL_FINISH_MT("); putsQuoted(protect(nodep->fileline()->filename())); puts(", "); diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 30698b340..53e8cac0c 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -524,6 +524,11 @@ 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; @@ -577,6 +582,19 @@ 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); @@ -596,6 +614,34 @@ class ForkVisitor final : public VNVisitor { return; } + // IEEE 1800-2023 9.3.2: In all cases, processes spawned by a fork-join block shall not + // start executing until the parent process is blocked or terminates. + // Because join and join_any block the parent process, it is only needed when join_none + // is used. + 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); + } + } + } + iterateAndNextNull(nodep->declsp()); iterateAndNextNull(nodep->stmtsp()); std::vector wrappedp; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index b677b19b9..cf31a07e3 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -622,6 +622,29 @@ 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; @@ -672,9 +695,10 @@ public: const AstCellInline* inlinep = lookupSymp ? VN_CAST(lookupSymp->nodep(), CellInline) : nullptr; // Replicated below - if (VSymEnt* const findSymp = findWithAltFallback(lookupSymp, ident, altIdent)) { - lookupSymp = findSymp; - } + VSymEnt* findSymp = findWithAltFallback(lookupSymp, ident, altIdent); + if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident); + if (findSymp) lookupSymp = unwrapForkParent(findSymp, ident); + // Check this module - cur modname else if ((cellp && cellp->modp()->origName() == ident) || (inlinep && inlinep->origModName() == ident)) { @@ -720,9 +744,11 @@ public: if (!lookupSymp) return nullptr; // Not found } } else { // Searching for middle submodule, must be a cell name - if (VSymEnt* const findSymp = findWithAltFlat(lookupSymp, ident, altIdent)) { - lookupSymp = findSymp; - } else { + VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent); + if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident); + if (findSymp) + lookupSymp = unwrapForkParent(findSymp, ident); + else { return nullptr; // Not found } } diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 1609b9972..b444ec9f3 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -23,6 +23,7 @@ #include "V3LinkParse.h" #include "V3Control.h" +#include "V3MemberMap.h" #include "V3Stats.h" #include @@ -46,6 +47,7 @@ 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 @@ -72,6 +74,9 @@ 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 @@ -105,6 +110,21 @@ 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); @@ -180,6 +200,38 @@ 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. @@ -791,7 +843,11 @@ class LinkParseVisitor final : public VNVisitor { } cleanFileline(nodep); iterateAndNextNull(nodep->stmtsp()); - if (AstFork* const forkp = VN_CAST(nodep, Fork)) iterateAndNextNull(forkp->forksp()); + if (AstFork* const forkp = VN_CAST(nodep, Fork)) { + iterateAndNextNull(forkp->forksp()); + if (!forkp->parentProcessp() && forkp->joinType().joinNone() && forkp->forksp()) + addForkParentProcess(forkp); + } } void visit(AstCase* nodep) override { V3Control::applyCase(nodep); @@ -954,7 +1010,10 @@ class LinkParseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit LinkParseVisitor(AstNetlist* rootp) { iterate(rootp); } + explicit LinkParseVisitor(AstNetlist* rootp) { + unprotectStdProcessHandle(); + iterate(rootp); + } ~LinkParseVisitor() override { V3Stats::addStatSum(V3Stats::STAT_SOURCE_MODULES, m_statModules); } diff --git a/src/V3MemberMap.h b/src/V3MemberMap.h index 5053c6e35..2a9561a12 100644 --- a/src/V3MemberMap.h +++ b/src/V3MemberMap.h @@ -89,6 +89,10 @@ private: for (AstNode* itemp = anodep->membersp(); itemp; itemp = itemp->nextp()) { memberInsert(mmapr, itemp); } + } else if (const AstPackage* const anodep = VN_CAST(nodep, Package)) { + for (AstNode* itemp = anodep->stmtsp(); itemp; itemp = itemp->nextp()) { + memberInsert(mmapr, itemp); + } } else { nodep->v3fatalSrc("Unsupported node type"); } diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index 5c0e40c7a..14988d1a7 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -340,4 +340,12 @@ 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/V3Timing.cpp b/src/V3Timing.cpp index 1ce3e1ac2..6c9f2cb46 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -469,6 +469,7 @@ class TimingControlVisitor final : public VNVisitor { AstScope* m_scopep = nullptr; // Current scope AstActive* m_activep = nullptr; // Current active AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under + bool m_hasProcess = false; // True if current scope has a VlProcess handle available int m_forkCnt = 0; // Number of forks inside a module bool m_underJumpBlock = false; // True if we are inside of a jump-block bool m_underProcedure = false; // True if we are under an always or initial @@ -654,26 +655,34 @@ class TimingControlVisitor final : public VNVisitor { } // Adds debug info to a hardcoded method call void addDebugInfo(AstCMethodHard* const methodp) const { - if (v3Global.opt.protectIds()) return; FileLine* const flp = methodp->fileline(); - AstCExpr* const ap = new AstCExpr{flp, '"' + flp->filenameEsc() + '"'}; + const bool protectIds = v3Global.opt.protectIds(); + AstCExpr* const ap + = new AstCExpr{flp, protectIds ? "VL_UNKNOWN" : '"' + flp->filenameEsc() + '"'}; ap->dtypeSetString(); methodp->addPinsp(ap); - AstCExpr* const bp = new AstCExpr{flp, cvtToStr(flp->lineno())}; + AstCExpr* const bp = new AstCExpr{flp, protectIds ? "0" : cvtToStr(flp->lineno())}; bp->dtypeSetString(); methodp->addPinsp(bp); } // Adds debug info to a trigSched.trigger() call void addEventDebugInfo(AstCMethodHard* const methodp, AstSenTree* const sentreep) const { - if (v3Global.opt.protectIds()) return; + if (v3Global.opt.protectIds()) { + FileLine* const flp = sentreep->fileline(); + AstCExpr* const descp = new AstCExpr{flp, "VL_UNKNOWN"}; + descp->dtypeSetString(); + methodp->addPinsp(descp); + addDebugInfo(methodp); + return; + } methodp->addPinsp(createEventDescription(sentreep)); 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 && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr"}; + AstCExpr* const ap + = new AstCExpr{flp, (m_procp && m_hasProcess) ? "vlProcess" : "nullptr"}; methodp->addPinsp(ap); } // Creates the fork handle type and returns it @@ -779,7 +788,9 @@ class TimingControlVisitor final : public VNVisitor { } void visit(AstNodeProcedure* nodep) override { VL_RESTORER(m_procp); + VL_RESTORER(m_hasProcess); m_procp = nodep; + m_hasProcess = hasFlags(nodep, T_HAS_PROC); VL_RESTORER(m_underProcedure); m_underProcedure = true; iterateChildren(nodep); @@ -801,7 +812,9 @@ class TimingControlVisitor final : public VNVisitor { void visit(AstAlways* nodep) override { if (nodep->user1SetOnce()) return; VL_RESTORER(m_procp); + VL_RESTORER(m_hasProcess); m_procp = nodep; + m_hasProcess = hasFlags(nodep, T_HAS_PROC); VL_RESTORER(m_underProcedure); m_underProcedure = true; // Workaround for killing `always` processes (doing that is pretty much UB) @@ -829,7 +842,9 @@ class TimingControlVisitor final : public VNVisitor { } void visit(AstCFunc* nodep) override { VL_RESTORER(m_procp); + VL_RESTORER(m_hasProcess); m_procp = nodep; + m_hasProcess = hasFlags(nodep, T_HAS_PROC); iterateChildren(nodep); if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); if (!(hasFlags(nodep, T_SUSPENDEE))) return; @@ -862,6 +877,7 @@ class TimingControlVisitor final : public VNVisitor { } } void visit(AstNodeCCall* nodep) override { + if (nodep->funcp()->needProcess()) m_hasProcess = true; if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable VNRelinker relinker; nodep->unlinkFrBack(&relinker); @@ -1165,12 +1181,12 @@ class TimingControlVisitor final : public VNVisitor { forkp->unlinkFrBack()}); } void visit(AstDisableFork* nodep) override { - if (hasFlags(m_procp, T_HAS_PROC)) return; + if (m_hasProcess) return; // never reached by any process; remove to avoid compilation error VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } void visit(AstWaitFork* nodep) override { - if (hasFlags(m_procp, T_HAS_PROC)) { + if (m_hasProcess) { FileLine* const flp = nodep->fileline(); AstCExpr* const exprp = new AstCExpr{flp, "vlProcess->completedFork()", 1}; AstWait* const waitp = new AstWait{flp, exprp, nullptr}; @@ -1233,8 +1249,10 @@ class TimingControlVisitor final : public VNVisitor { } void visit(AstBegin* nodep) override { VL_RESTORER(m_procp); + VL_RESTORER(m_hasProcess); + m_hasProcess |= hasFlags(nodep, T_HAS_PROC); m_procp = nodep; - if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); + if (m_hasProcess) nodep->setNeedProcess(); iterateChildren(nodep); } void visit(AstFork* nodep) override { diff --git a/src/verilog.y b/src/verilog.y index 7c3a90de1..5de8b1f5d 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3400,23 +3400,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 { - $$ = new AstFork{$1, $5, $2 ? *$2 : ""}; - GRAMMARP->endLabel($6, $$, $6); - $$->addDeclsp($3); - $$->addForksp(V3ParseGrammar::wrapInBegin($4)); + AstFork* const forkp = new AstFork{$1, $5, $2 ? *$2 : ""}; + GRAMMARP->endLabel($6, forkp, $6); + forkp->addDeclsp($3); + $$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $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 { - $$ = new AstFork{$3, $6, *$1}; - GRAMMARP->endLabel($7, $$, $7); - $$->addDeclsp($4); - $$->addForksp(V3ParseGrammar::wrapInBegin($5)); + AstFork* const forkp = new AstFork{$3, $6, *$1}; + GRAMMARP->endLabel($7, forkp, $7); + forkp->addDeclsp($4); + $$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $5); } ; diff --git a/test_regress/t/t_disable_task_simple.v b/test_regress/t/t_disable_task_simple.v index 5eb93fbe8..d4983967f 100644 --- a/test_regress/t/t_disable_task_simple.v +++ b/test_regress/t/t_disable_task_simple.v @@ -28,6 +28,7 @@ class Cls; y = 2; end join_none + #1; endtask endclass diff --git a/test_regress/t/t_fork_block_item_declaration.v b/test_regress/t/t_fork_block_item_declaration.v index 93cd19215..6f00d126e 100644 --- a/test_regress/t/t_fork_block_item_declaration.v +++ b/test_regress/t/t_fork_block_item_declaration.v @@ -17,6 +17,7 @@ class Foo; $stop; end join_none : frk + #1; endtask endclass diff --git a/test_regress/t/t_fork_cfunc_finish.py b/test_regress/t/t_fork_cfunc_finish.py new file mode 100755 index 000000000..1c061c8c9 --- /dev/null +++ b/test_regress/t/t_fork_cfunc_finish.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_cfunc_finish.v b/test_regress/t/t_fork_cfunc_finish.v new file mode 100644 index 000000000..661718f14 --- /dev/null +++ b/test_regress/t/t_fork_cfunc_finish.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +module t; + export "DPI-C" task cfunc_finish; // this is just so the task becomes AstCFunc, we don't really use the export + task cfunc_finish; + $finish; + endtask + + initial begin + fork + cfunc_finish(); + join_none + #1 $stop; + end +endmodule diff --git a/test_regress/t/t_fork_delay.py b/test_regress/t/t_fork_delay.py new file mode 100755 index 000000000..1c061c8c9 --- /dev/null +++ b/test_regress/t/t_fork_delay.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_delay.v b/test_regress/t/t_fork_delay.v new file mode 100644 index 000000000..50b19f307 --- /dev/null +++ b/test_regress/t/t_fork_delay.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +module t; + integer i=0; + initial begin + fork + i=1; + join_none + if(i==1) $stop; + $finish; + end +endmodule diff --git a/test_regress/t/t_fork_delay_finish.py b/test_regress/t/t_fork_delay_finish.py new file mode 100755 index 000000000..1c061c8c9 --- /dev/null +++ b/test_regress/t/t_fork_delay_finish.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_delay_finish.v b/test_regress/t/t_fork_delay_finish.v new file mode 100644 index 000000000..5da994438 --- /dev/null +++ b/test_regress/t/t_fork_delay_finish.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, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +module t; + bit flag; + initial begin + fork begin + $stop; + end join_none + $finish; + end +endmodule diff --git a/test_regress/t/t_fork_dynscope_out.v b/test_regress/t/t_fork_dynscope_out.v index ad7f1ce68..c4e25a014 100644 --- a/test_regress/t/t_fork_dynscope_out.v +++ b/test_regress/t/t_fork_dynscope_out.v @@ -20,9 +20,10 @@ module t; fork p = 1; join_none + #0; endtask task t2(output q); - q <= 1; + q = 1; endtask endmodule diff --git a/test_regress/t/t_process_fork.v b/test_regress/t/t_process_fork.v index b4a7a18ab..90020c506 100644 --- a/test_regress/t/t_process_fork.v +++ b/test_regress/t/t_process_fork.v @@ -8,12 +8,15 @@ module t; process job[] = new [8]; initial begin - foreach (job[j]) fork - begin - $write("job started\n"); - job[j] = process::self(); - end - join_none + foreach (job[j]) begin + fork + begin + $write("job started\n"); + job[j] = process::self(); + end + join_none + #0; + end foreach (job[j]) begin wait (job[j]); end diff --git a/test_regress/t/t_process_fork_block.out b/test_regress/t/t_process_fork_block.out new file mode 100644 index 000000000..f6963ba73 --- /dev/null +++ b/test_regress/t/t_process_fork_block.out @@ -0,0 +1,4 @@ +job started +all jobs started +all jobs finished +*-* All Finished *-* diff --git a/test_regress/t/t_process_fork_block.py b/test_regress/t/t_process_fork_block.py new file mode 100755 index 000000000..dcb1ff476 --- /dev/null +++ b/test_regress/t/t_process_fork_block.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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_process_fork_block.v b/test_regress/t/t_process_fork_block.v new file mode 100644 index 000000000..ab6ed5d59 --- /dev/null +++ b/test_regress/t/t_process_fork_block.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + process job; + + initial begin + process p1 = process::self(); + fork + begin + wait(p1.status() != process::RUNNING); + $write("job started\n"); + job = process::self(); + end + join_none + wait (job); + $write("all jobs started\n"); + job.await(); + $write("all jobs finished\n"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_process_propagation.v b/test_regress/t/t_process_propagation.v index 812b12ccb..70973bcba 100644 --- a/test_regress/t/t_process_propagation.v +++ b/test_regress/t/t_process_propagation.v @@ -50,7 +50,7 @@ module t(); bar.ewait(); end join_none - + #1; p.kill(); ->evt1; diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 600ec9aa6..c5b101bbd 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -2,7 +2,10 @@ -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 @@ -1207,7 +1210,12 @@ -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); , ); )) 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 @@ -1217,8 +1225,13 @@ -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__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); , ); )) at t/t_timing_class.v:224 awaiting resumption -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()) @@ -1228,9 +1241,11 @@ -V{t#,#}+ Vt_timing_debug2___024root___timing_resume -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__act -V{t#,#} No suspended processes waiting for dynamic trigger evaluation @@ -1271,15 +1286,15 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume -V{t#,#} Delayed processes: --V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:224 -V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:131 +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:224 -V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:136 -V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:190 -V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:274 -V{t#,#} Resuming delayed processes --V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224 -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__act -V{t#,#} No suspended processes waiting for dynamic trigger evaluation @@ -1327,12 +1342,34 @@ -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_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign +-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); , ); )) 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__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); , ); )) at t/t_timing_class.v:229 awaiting resumption +-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___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act +-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-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 +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#} No suspended processes waiting for dynamic trigger evaluation -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act diff --git a/test_regress/t/t_timing_fork_comb.py b/test_regress/t/t_timing_fork_comb.py index 3f559445e..c53b55262 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 -Wno-UNOPTFLAT"]) +test.compile(verilator_flags2=["--binary"]) test.execute() diff --git a/test_regress/t/t_timing_fork_comb_bad.py b/test_regress/t/t_timing_fork_comb_bad.py deleted file mode 100755 index 1b5d6f77c..000000000 --- a/test_regress/t/t_timing_fork_comb_bad.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/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()