diff --git a/src/V3CUse.cpp b/src/V3CUse.cpp index 0bbab0886..e53c8b3d1 100644 --- a/src/V3CUse.cpp +++ b/src/V3CUse.cpp @@ -64,7 +64,9 @@ class CUseVisitor final : public VNVisitor { // VISITORS void visit(AstClassRefDType* nodep) override { - if (nodep->user1SetOnce()) return; // Process once + if (nodep->user1()) return; // Process once + if (!m_dtypesImplOnly) // We might need to revisit this type for interface + nodep->user1(true); addNewUse(nodep, VUseType::INT_FWD_CLASS, nodep->classp()->name()); } void visit(AstCFunc* nodep) override { diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 427775cdc..d0829181a 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -18,11 +18,23 @@ // // Each module: // Look for FORKs [JOIN_NONE]/[JOIN_ANY] +// VARREF(var) -> MEMBERSEL(var->name, VARREF(dynscope)) (for write/RW refs) // FORK(stmts) -> TASK(stmts), FORK(TASKREF(inits)) // // FORKs that spawn tasks which might outlive their parents require those // tasks to carry their own frames and as such they require their own // variable scopes. +// There are two mechanisms that work together to achieve that. ForkVisitor +// moves bodies of forked prcesses into new tasks, which results in them getting their +// own scopes. The original statements get replaced with a call to the task which +// passes the required variables by value. +// The second mechanism, DynScopeVisitor, is designed to handle variables which can't be +// captured by value and instead require a reference. Those variables get moved into an +// "anonymous" object, ie. a class with appropriate fields gets generated and an object +// of this class gets instantiated in place of the original variable declarations. +// Any references to those variables are replaced with references to the object's field. +// Since objects are reference-counted this ensures that the variables are accessible +// as long as both the parent and the forked processes require them to be. // //************************************************************************* @@ -34,12 +46,344 @@ #include "V3Ast.h" #include "V3AstNodeExpr.h" #include "V3Global.h" +#include "V3MemberMap.h" #include +#include #include +#include VL_DEFINE_DEBUG_FUNCTIONS; +class ForkDynScopeInstance final { +public: + AstClass* m_classp = nullptr; // Class for holding variables of dynamic scope + AstClassRefDType* m_refDTypep = nullptr; // RefDType for the above + AstVar* m_handlep = nullptr; // Class handle for holding variables of dynamic scope + + // True if the instance exists + bool initialized() const { return m_classp != nullptr; } +}; + +class ForkDynScopeFrame final { +private: + // MEMBERS + AstNodeModule* const m_modp; // Module to insert the scope into + AstNode* const m_procp; // Procedure/block associated with that dynscope + std::set m_captures; // Variables to be moved into the dynscope + ForkDynScopeInstance m_instance; // Nodes to be injected into the AST to create the dynscope + +public: + ForkDynScopeFrame(AstNodeModule* modp, AstNode* procp) + : m_modp{modp} + , m_procp{procp} {} + + ForkDynScopeInstance& createInstancePrototype() { + UASSERT_OBJ(!m_instance.initialized(), m_procp, "Dynamic scope already instantiated."); + + m_instance.m_classp + = new AstClass{m_procp->fileline(), generateDynScopeClassName(m_procp)}; + m_instance.m_refDTypep + = new AstClassRefDType{m_procp->fileline(), m_instance.m_classp, nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(m_instance.m_refDTypep); + 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->lifetime(VLifetime::AUTOMATIC); + + return m_instance; + } + + const ForkDynScopeInstance& instance() const { return m_instance; } + void captureVarInsert(AstVar* varp) { m_captures.insert(varp); } + bool captured(AstVar* varp) { return m_captures.count(varp) != 0; } + AstNode* procp() const { return m_procp; } + + void populateClass() { + UASSERT_OBJ(m_instance.initialized(), m_procp, "No DynScope prototype"); + + // Move variables into the class + for (AstVar* varp : m_captures) { + if (varp->direction() == VDirection::INPUT) { + varp = varp->cloneTree(false); + varp->direction(VDirection::NONE); + } else { + varp->unlinkFrBack(); + } + varp->funcLocal(false); + varp->varType(VVarType::MEMBER); + varp->lifetime(VLifetime::AUTOMATIC); + varp->usedLoopIdx(false); // No longer unrollable + m_instance.m_classp->addStmtsp(varp); + } + + // Create class's constructor + AstFunc* const newp + = new AstFunc{m_instance.m_classp->fileline(), "new", nullptr, nullptr}; + newp->isConstructor(true); + newp->classMethod(true); + newp->dtypep(newp->findVoidDType()); + m_instance.m_classp->addStmtsp(newp); + } + + void linkNodes(VMemberMap& memberMap) { + UASSERT_OBJ(m_instance.initialized(), m_procp, "No dynamic scope prototype"); + UASSERT_OBJ(!linked(), m_instance.m_handlep, "Handle already linked"); + + AstNode* stmtp = getProcStmts(); + UASSERT(stmtp, "trying to instantiate dynamic scope while not under proc"); + VNRelinker stmtpHandle; + stmtp->unlinkFrBackWithNext(&stmtpHandle); + + // Find node after last variable declaration + AstNode* initp = stmtp; + while (initp && VN_IS(initp, Var)) initp = initp->nextp(); + UASSERT(stmtp, "Procedure lacks body"); + UASSERT(initp, "Procedure lacks statements besides declarations"); + + AstNew* const newp = new AstNew{m_procp->fileline(), nullptr}; + newp->taskp(VN_AS(memberMap.findMember(m_instance.m_classp, "new"), NodeFTask)); + newp->dtypep(m_instance.m_refDTypep); + newp->classOrPackagep(m_instance.m_classp); + + AstNode* const asgnp = new AstAssign{ + m_procp->fileline(), + new AstVarRef{m_procp->fileline(), m_instance.m_handlep, VAccess::WRITE}, newp}; + + AstNode* initsp = nullptr; // Arguments need to be copied + for (AstVar* varp : m_captures) { + if (varp->direction() != VDirection::INPUT) continue; + + AstMemberSel* const memberselp = new AstMemberSel{ + varp->fileline(), + new AstVarRef{varp->fileline(), m_instance.m_handlep, VAccess::WRITE}, + varp->dtypep()}; + memberselp->name(varp->name()); + memberselp->varp(VN_AS(memberMap.findMember(m_instance.m_classp, varp->name()), Var)); + AstNode* initAsgnp + = new AstAssign{varp->fileline(), memberselp, + new AstVarRef{varp->fileline(), varp, VAccess::READ}}; + initsp = AstNode::addNext(initsp, initAsgnp); + } + if (initsp) AstNode::addNext(asgnp, initsp); + + if (initp != stmtp) { + initp->addHereThisAsNext(asgnp); + } else { + AstNode::addNext(asgnp, static_cast(initp)); + stmtp = asgnp; + } + + AstNode::addNext(static_cast(m_instance.m_handlep), stmtp); + stmtpHandle.relink(m_instance.m_handlep); + m_modp->addStmtsp(m_instance.m_classp); + } + + bool linked() const { return m_instance.initialized() && m_instance.m_handlep->backp(); } + +private: + static string generateDynScopeClassName(const AstNode* fromp) { + string n = "__VDynScope__" + (!fromp->name().empty() ? (fromp->name() + "__") : "ANON__") + + cvtToHex(fromp); + return n; + } + + static string generateDynScopeHandleName(const AstNode* fromp) { + return "__VDynScope_" + (fromp->name().empty() ? cvtToHex(fromp) : fromp->name()); + } + + AstNode* getProcStmts() { + AstNode* stmtsp = nullptr; + if (!m_procp) return nullptr; + if (AstBegin* beginp = VN_CAST(m_procp, Begin)) { + stmtsp = beginp->stmtsp(); + } else if (AstNodeFTask* taskp = VN_CAST(m_procp, NodeFTask)) { + stmtsp = taskp->stmtsp(); + } else { + v3fatal("m_procp is not a begin block or a procedure"); + } + return stmtsp; + } +}; + +//###################################################################### +// Dynamic scope visitor, creates classes and objects for dynamic scoping of variables and +// replaces references to varibles that need a dynamic scope with references to object's +// members + +class DynScopeVisitor final : public VNVisitor { +private: + // NODE STATE + // AstVar::user1() -> int, timing-control fork nesting level of that variable + // AstVarRef::user2() -> bool, 1 = Node is a class handle reference. The handle gets + // modified in the context of this reference. + const VNUser1InUse m_inuser1; + const VNUser2InUse m_inuser2; + + // STATE + AstNodeModule* m_modp = nullptr; // Module we are currently under + AstNode* m_procp = nullptr; // Function/task/block we are currently under + std::map + m_frames; // Mapping from nodes to related DynScopeFrames + VMemberMap m_memberMap; // Class member look-up + int m_forkDepth = 0; // Number of asynchronous forks we are currently under + bool m_afterTimingControl = false; // A timing control might've be executed in the current + // process + + // METHODS + + ForkDynScopeFrame* frameOf(AstNode* nodep) { + auto frameIt = m_frames.find(nodep); + if (frameIt == m_frames.end()) return nullptr; + return frameIt->second; + } + + const ForkDynScopeFrame* frameOf(AstNode* nodep) const { + auto frameIt = m_frames.find(nodep); + if (frameIt == m_frames.end()) return nullptr; + return frameIt->second; + } + + ForkDynScopeFrame* pushDynScopeFrame() { + ForkDynScopeFrame* const frame = new ForkDynScopeFrame{m_modp, m_procp}; + auto r = m_frames.emplace(std::make_pair(m_procp, frame)); + UASSERT_OBJ(r.second, m_modp, "Procedure already contains a frame"); + return frame; + } + + void replaceWithMemberSel(AstVarRef* refp, const ForkDynScopeInstance& dynScope) { + VNRelinker handle; + refp->unlinkFrBack(&handle); + AstMemberSel* const membersel = new AstMemberSel{ + refp->fileline(), new AstVarRef{refp->fileline(), dynScope.m_handlep, refp->access()}, + refp->dtypep()}; + membersel->name(refp->varp()->name()); + if (refp->varp()->direction() == VDirection::INPUT) { + membersel->varp( + VN_AS(m_memberMap.findMember(dynScope.m_classp, refp->varp()->name()), Var)); + } else { + membersel->varp(refp->varp()); + } + handle.relink(membersel); + VL_DO_DANGLING(refp->deleteTree(), refp); + } + + static bool hasAsyncFork(AstNode* nodep) { + bool afork = false; + nodep->foreach([&](AstFork* forkp) { + if (!forkp->joinType().join()) afork = true; + }); + return afork; + } + + void bindNodeToDynScope(AstNode* nodep, ForkDynScopeFrame* frame) { + m_frames.emplace(std::make_pair(nodep, frame)); + } + + bool needsDynScope(const AstVarRef* refp) const { + return + // Can this variable escape the scope + ((m_forkDepth > refp->varp()->user1()) && refp->varp()->isFuncLocal()) + && ( + // Is it mutated + (refp->varp()->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW()) + // Or is it after a timing-control event + || m_afterTimingControl); + } + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + if (!VN_IS(nodep, Class)) m_modp = nodep; + iterateChildren(nodep); + } + void visit(AstNodeFTask* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; + if (hasAsyncFork(nodep)) pushDynScopeFrame(); + iterateChildren(nodep); + } + void visit(AstBegin* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; + if (hasAsyncFork(nodep)) pushDynScopeFrame(); + iterateChildren(nodep); + } + void visit(AstFork* nodep) override { + VL_RESTORER(m_forkDepth); + if (!nodep->joinType().join()) ++m_forkDepth; + + const bool oldAfterTimingControl = m_afterTimingControl; + for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + m_afterTimingControl = false; + iterate(stmtp); + } + m_afterTimingControl = oldAfterTimingControl; + if (nodep->isTimingControl()) m_afterTimingControl = true; + } + void visit(AstNodeFTaskRef* nodep) override { + visit(static_cast(nodep)); + // We are before V3Timing, so unfortnately we need to treat any calls as suspending, + // just to be safe. This might be improved if we could propagate suspendability + // before doing all the other timing-related stuff. + m_afterTimingControl = true; + } + void visit(AstVar* nodep) override { + nodep->user1(m_forkDepth); + ForkDynScopeFrame* const framep = frameOf(m_procp); + if (!framep) return; // Cannot be legally referenced from a fork + bindNodeToDynScope(nodep, framep); + } + void visit(AstVarRef* nodep) override { + ForkDynScopeFrame* const framep = frameOf(nodep->varp()); + if (!framep) return; + + if (needsDynScope(nodep)) { + if (!framep->instance().initialized()) framep->createInstancePrototype(); + framep->captureVarInsert(nodep->varp()); + } + bindNodeToDynScope(nodep, framep); + } + void visit(AstAssign* nodep) override { + if (VN_IS(nodep->lhsp(), VarRef) && nodep->lhsp()->isClassHandleValue()) { + nodep->lhsp()->user2(true); + } + visit(static_cast(nodep)); + } + void visit(AstNode* nodep) override { + if (nodep->isTimingControl()) m_afterTimingControl = true; + iterateChildren(nodep); + } + +public: + // CONSTRUCTORS + DynScopeVisitor(AstNetlist* nodep) { + // Create Dynamic scope class prototypes and objects + visit(nodep); + + // Commit changes to AST + bool typesAdded = false; + for (auto frameIt : m_frames) { + ForkDynScopeFrame* frame = frameIt.second; + if (!frame->instance().initialized()) continue; + + if (!frame->linked()) { + frame->populateClass(); + frame->linkNodes(m_memberMap); + typesAdded = true; + } + + if (AstVarRef* refp = VN_CAST(frameIt.first, VarRef)) { + if (frame->captured(refp->varp())) replaceWithMemberSel(refp, frame->instance()); + } + } + + if (typesAdded) v3Global.rootp()->typeTablep()->repairCache(); + } + ~DynScopeVisitor() override = default; +}; + //###################################################################### // Fork visitor, transforms asynchronous blocks into separate tasks @@ -59,10 +403,10 @@ private: AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables std::set m_forkLocalsp; // Variables local to a given fork AstArg* m_capturedVarRefsp = nullptr; // References to captured variables (as args) - int m_createdTasksCount = 0; // Number of tasks created by this visitor // METHODS - AstVar* captureRef(AstNodeExpr* refp) { + + AstVar* captureRef(AstVarRef* refp) { AstVar* varp = nullptr; for (varp = m_capturedVarsp; varp; varp = VN_AS(varp->nextp(), Var)) if (varp->name() == refp->name()) break; @@ -74,20 +418,20 @@ private: varp->lifetime(VLifetime::AUTOMATIC); m_capturedVarsp = AstNode::addNext(m_capturedVarsp, varp); // Use the original ref as an argument for call - AstArg* arg = new AstArg{refp->fileline(), refp->name(), refp->cloneTree(false)}; - m_capturedVarRefsp = AstNode::addNext(m_capturedVarRefsp, arg); + m_capturedVarRefsp + = AstNode::addNext(m_capturedVarRefsp, new AstArg{refp->fileline(), refp->name(), + refp->cloneTree(false)}); } return varp; } - AstTask* makeTask(FileLine* fl, AstNode* stmtsp, std::string name) { + AstTask* makeTask(FileLine* fl, AstNode* stmtsp, string name) { stmtsp = AstNode::addNext(static_cast(m_capturedVarsp), stmtsp); AstTask* const taskp = new AstTask{fl, name, stmtsp}; - ++m_createdTasksCount; return taskp; } - std::string generateTaskName(AstNode* fromp, std::string kind) { + string generateTaskName(AstNode* fromp, string kind) { // TODO: Ensure no collisions occur return "__V" + kind + (!fromp->name().empty() ? (fromp->name() + "__") : "UNNAMED__") + cvtToHex(fromp); @@ -122,16 +466,16 @@ private: if (AstBegin* beginp = VN_CAST(nodep, Begin)) { UASSERT(beginp->stmtsp(), "No stmtsp\n"); - const std::string taskName = generateTaskName(beginp, "__FORK_BEGIN_"); + const string taskName = generateTaskName(beginp, "__FORK_BEGIN_"); taskp = makeTask(beginp->fileline(), beginp->stmtsp()->unlinkFrBackWithNext(), taskName); beginp->unlinkFrBack(&handle); VL_DO_DANGLING(beginp->deleteTree(), beginp); } else if (AstNodeStmt* stmtp = VN_CAST(nodep, NodeStmt)) { - const std::string taskName = generateTaskName(stmtp, "__FORK_STMT_"); + const string taskName = generateTaskName(stmtp, "__FORK_STMT_"); taskp = makeTask(stmtp->fileline(), stmtp->unlinkFrBack(&handle), taskName); } else if (AstFork* forkp = VN_CAST(nodep, Fork)) { - const std::string taskName = generateTaskName(forkp, "__FORK_NESTED_"); + const string taskName = generateTaskName(forkp, "__FORK_NESTED_"); taskp = makeTask(forkp->fileline(), forkp->unlinkFrBack(&handle), taskName); } @@ -139,7 +483,8 @@ private: AstTaskRef* const taskrefp = new AstTaskRef{nodep->fileline(), taskp->name(), m_capturedVarRefsp}; - AstStmtExpr* const taskcallp = new AstStmtExpr{nodep->fileline(), taskrefp}; + taskrefp->taskp(taskp); + AstStmtExpr* const taskcallp = taskrefp->makeStmt(); // Replaced nodes will be revisited, so we don't need to "lift" the arguments // as captures in case of nested forks. handle.relink(taskcallp); @@ -223,23 +568,19 @@ public: // CONSTRUCTORS ForkVisitor(AstNetlist* nodep) { visit(nodep); } ~ForkVisitor() override = default; - - // UTILITY - int createdTasksCount() { return m_createdTasksCount; } }; //###################################################################### // Fork class functions -int V3Fork::makeTasks(AstNetlist* nodep) { - int createdTasksCount; - +void V3Fork::makeDynamicScopes(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { - ForkVisitor fork_visitor(nodep); - createdTasksCount = fork_visitor.createdTasksCount(); - } - V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3); - - return createdTasksCount; + { DynScopeVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("fork_dynscope", 0, dumpTreeLevel() >= 3); +} + +void V3Fork::makeTasks(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { ForkVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3); } diff --git a/src/V3Fork.h b/src/V3Fork.h index 8554fa75f..e36b530cb 100644 --- a/src/V3Fork.h +++ b/src/V3Fork.h @@ -27,9 +27,12 @@ class AstNetlist; class V3Fork final { public: - // Create tasks out of begin blocks that can outlive processes in which they were forked. + // Move/copy variables to "anonymous" objects if their lifetime might exceed the scope of a + // procedure that declared them. Update the references apropriately. + static void makeDynamicScopes(AstNetlist* nodep); + // Create tasks out of blocks/statments that can outlive processes in which they were forked. // Return value: number of tasks created - static int makeTasks(AstNetlist* nodep); + static void makeTasks(AstNetlist* nodep); }; #endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index c1654b524..243b4c4ae 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -222,9 +222,9 @@ static void process() { V3Inst::dearrayAll(v3Global.rootp()); V3LinkDot::linkDotArrayed(v3Global.rootp()); - // Create dedicated tasks for fork..join_any / fork_join_none processes - if (V3Fork::makeTasks(v3Global.rootp())) - V3LinkDot::linkDotPrimary(v3Global.rootp()); // Link newly created tasks + // Generate classes and tasks required to maintain proper lifetimes for references in forks + V3Fork::makeDynamicScopes(v3Global.rootp()); + V3Fork::makeTasks(v3Global.rootp()); // Task inlining & pushing BEGINs names to variables/cells // Begin processing must be after Param, before module inlining diff --git a/test_regress/t/t_clocking_sched_timing_forkproc.out b/test_regress/t/t_clocking_sched_timing_forkproc.out index 8094d59d9..f0fee8b8e 100755 --- a/test_regress/t/t_clocking_sched_timing_forkproc.out +++ b/test_regress/t/t_clocking_sched_timing_forkproc.out @@ -1,5 +1,58 @@ -%Error: t/t_clocking_sched.v:11:9: Input/output/inout does not appear in port list: 'clk' - : ... In instance t - 11 | input clk; - | ^~~ -%Error: Exiting due to +0 | posedge +0 | cb.y=0 +0 | b=0 +0 | z<=0 +0 | x<=0 +0 | y=0 +0 | c<=0 +0 | c<=1 +0 | cb.a=1 +0 | cb.b=1 +0 | posedge +0 | x<=1 +0 | y=1 +0 | c<=0 +0 | cb.a=0 +0 | cb.b=1 +0 | cb.y=1 +0 | b=1 +0 | x<=0 +0 | y=0 +0 | 0 1 0 0 0 0 +2 | z<=1 +3 | posedge +3 | c<=1 +3 | cb.a=1 +3 | cb.b=1 +3 | cb.y=0 +3 | b=0 +3 | posedge +3 | x<=1 +3 | y=1 +3 | c<=0 +3 | cb.a=0 +3 | cb.b=1 +3 | cb.y=1 +3 | b=1 +3 | x<=0 +3 | y=0 +3 | 0 1 0 0 0 1 +5 | posedge +5 | z<=0 +5 | c<=1 +5 | cb.a=1 +5 | cb.b=1 +5 | cb.y=0 +5 | b=0 +5 | posedge +5 | x<=1 +5 | y=1 +5 | c<=0 +5 | cb.a=0 +5 | cb.b=1 +5 | cb.y=1 +5 | b=1 +5 | x<=0 +5 | y=0 +5 | 0 1 0 0 0 0 +*-* All Finished *-* diff --git a/test_regress/t/t_clocking_sched_timing_forkproc.pl b/test_regress/t/t_clocking_sched_timing_forkproc.pl index d1bb14c97..7c1c36bef 100755 --- a/test_regress/t/t_clocking_sched_timing_forkproc.pl +++ b/test_regress/t/t_clocking_sched_timing_forkproc.pl @@ -7,14 +7,18 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(linter => 1); +scenarios(vlt => 1); top_filename("t/t_clocking_sched.v"); -lint( - verilator_flags2 => ["--timing", "--ftaskify-all-forked"], - expect_filename => $Self->{golden_filename}, - fails => 1, +compile( + timing_loop => 1, + verilator_flags2 => ["--timing"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename} ); ok(1); diff --git a/test_regress/t/t_timing_fork_unsup.pl b/test_regress/t/t_fork_dynscope.pl similarity index 65% rename from test_regress/t/t_timing_fork_unsup.pl rename to test_regress/t/t_fork_dynscope.pl index 8ab3c0995..b8493bd06 100755 --- a/test_regress/t/t_timing_fork_unsup.pl +++ b/test_regress/t/t_fork_dynscope.pl @@ -2,18 +2,21 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2022 by Antmicro Ltd. This program is free software; you +# Copyright 2023 by Wilson Snyder. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(linter => 1); +scenarios(simulator => 1); -lint( - verilator_flags2 => ["--timing"], - fails => 1, - expect_filename => $Self->{golden_filename}, +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_fork_dynscope.v b/test_regress/t/t_fork_dynscope.v new file mode 100644 index 000000000..e1264e238 --- /dev/null +++ b/test_regress/t/t_fork_dynscope.v @@ -0,0 +1,59 @@ +// 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 + +class Foo; + task do_something(int arg_v); + int dynscope_var; + int x; + dynscope_var = 0; + + fork + #10 begin + x = 0; + // Test capturing a variable that needs to be modified + $display("Incremented dynscope_var: %d", ++dynscope_var); + if (dynscope_var != 1) + $stop; + + // Check nested access + fork + #10 begin + $display("Incremented x: %d", ++x); + $display("Incremented dynscope_var: %d", ++dynscope_var); + if (dynscope_var != 2) + $stop; + end + join_none + end + #10 begin + // Same as the first check, but with an argument + // (so it needs to be copied to the dynamic scope instead of being moved there) + $display("Incremented arg_v: %d", ++arg_v); + if (arg_v != 2) + $stop; + end + join_none + + // Check if regular access to arg_v has been substituted with access to its copy from + // a dynamic scope + $display("Incremented arg_v: %d", ++arg_v); + if (arg_v != 1) + $stop; + endtask +endclass + +module t(); + initial begin + Foo foo; + foo = new; + foo.do_something(0); + + #99 begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_timing_dynscope.pl b/test_regress/t/t_timing_dynscope.pl new file mode 100755 index 000000000..b8493bd06 --- /dev/null +++ b/test_regress/t/t_timing_dynscope.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_dynscope.v b/test_regress/t/t_timing_dynscope.v new file mode 100644 index 000000000..73cc70d37 --- /dev/null +++ b/test_regress/t/t_timing_dynscope.v @@ -0,0 +1,41 @@ +// 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 + +`timescale 1ns / 1ns + +static int counts[10]; + +class Foo; + static task do_something(); + for (int i = 0; i < 10; i++) begin // Should create a dynamic scope for `i` + int ci = i; // Should create another dynamic scope for `ci`, local to the begin block + fork begin + #10; + $display("ci: %d, i: %d", ci, i); + if (i != 10) + $stop; + if (counts[ci-1]++ > 0) + $stop; + end join_none + ci++; + end + endtask +endclass + +module t(); + initial begin + int desired_counts[10]; + counts = '{10{0}}; + desired_counts = '{10{1}}; + + Foo::do_something(); + #20; + if (counts != desired_counts) + $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_fork_unsup.out b/test_regress/t/t_timing_fork_unsup.out deleted file mode 100644 index 399dfd1c3..000000000 --- a/test_regress/t/t_timing_fork_unsup.out +++ /dev/null @@ -1,17 +0,0 @@ -%Error-LIFETIME: t/t_timing_fork_unsup.v:12:12: Invalid reference: Process might outlive variable `x`. - : ... In instance t - : ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members. - 12 | #6 x = 4; - | ^ - ... For error description see https://verilator.org/warn/LIFETIME?v=latest -%Error-LIFETIME: t/t_timing_fork_unsup.v:13:12: Invalid reference: Process might outlive variable `x`. - : ... In instance t - : ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members. - 13 | #2 x++; - | ^ -%Error-LIFETIME: t/t_timing_fork_unsup.v:14:9: Invalid reference: Process might outlive variable `x`. - : ... In instance t - : ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members. - 14 | x = #4 x * 3; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_fork_unsup.v b/test_regress/t/t_timing_fork_unsup.v deleted file mode 100644 index 72a698e6d..000000000 --- a/test_regress/t/t_timing_fork_unsup.v +++ /dev/null @@ -1,24 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2022 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - class C; - task f; - int x = 0; - fork - #6 x = 4; - #2 x++; - x = #4 x * 3; - join_none - x = 1; - endtask - endclass - - initial begin - C o = new; - o.f; - end -endmodule