V3Fork - transform processes that can outlive their parents into separate tasks (#4253)
Signed-off-by: Krzysztof Boronski <kboronski@antmicro.com>
This commit is contained in:
parent
b517fb5ee3
commit
21c01ba97b
|
|
@ -876,6 +876,102 @@ List Of Warnings
|
||||||
Ignoring this warning will only suppress the lint check; it will
|
Ignoring this warning will only suppress the lint check; it will
|
||||||
simulate correctly.
|
simulate correctly.
|
||||||
|
|
||||||
|
.. option:: LIFETIME
|
||||||
|
|
||||||
|
Error when a variable is referenced in a process that can outlive the process
|
||||||
|
in which it was declared. This can happen when using 'fork..join_none' or
|
||||||
|
'fork..join_any' blocks, which spawn process that can outlive their parents.
|
||||||
|
This error occurs only when Verilator can't replace the reference with a
|
||||||
|
reference to copy of this variable, local to the forked process. For example:
|
||||||
|
|
||||||
|
.. code-block:: sv
|
||||||
|
:linenos:
|
||||||
|
:emphasize-lines: 3
|
||||||
|
|
||||||
|
task foo(int local_var);
|
||||||
|
fork
|
||||||
|
#10 local_var++;
|
||||||
|
#20 $display("local_var = %d", local_var);
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
|
In the example above 'local_var' exists only within scope of 'foo', once foo
|
||||||
|
finishes, the stack frame containing 'i' gets removed. However, the process
|
||||||
|
forked from foo continues, as it contains a delay. After 10 units of time
|
||||||
|
pass, this process attempts to modify 'local_var'. However, this variable no
|
||||||
|
longer exits. It can't be made local to the forked process upon spawning, because
|
||||||
|
it's modified and can be referenced somewhere else, for example in the other
|
||||||
|
forked process, that was delayed by 20 units of time in this example. Thus,
|
||||||
|
there's no viable stack allocation for it.
|
||||||
|
|
||||||
|
In order to fix it, you can create a local copy of the varible for
|
||||||
|
each process, if you don't intend to share its state outside of those processes.
|
||||||
|
|
||||||
|
Eg.:
|
||||||
|
|
||||||
|
.. code-block:: sv
|
||||||
|
:linenos:
|
||||||
|
:emphasize-lines: 4
|
||||||
|
|
||||||
|
task foo(int local_var);
|
||||||
|
fork
|
||||||
|
#10 begin
|
||||||
|
int forked_var = local_var;
|
||||||
|
forked_var++;
|
||||||
|
end
|
||||||
|
#20 begin
|
||||||
|
// Note that we are going to print the original value here,
|
||||||
|
// as `forked_var`is a local copy that was initialized while
|
||||||
|
// `foo` was still alive.
|
||||||
|
int forked_var = local_var;
|
||||||
|
$display("forked_var = %d", forked_var)
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
|
If you need to share its state, another strategy is to ensure it's allocated
|
||||||
|
statically:
|
||||||
|
|
||||||
|
.. code-block:: sv
|
||||||
|
:linenos:
|
||||||
|
:emphasize-lines: 1
|
||||||
|
|
||||||
|
int static_var;
|
||||||
|
|
||||||
|
task foo();
|
||||||
|
fork
|
||||||
|
#10 static_var++;
|
||||||
|
#20 $display("static_var = %d", static_var);
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
|
However, if you need to be able to instantiate at runtime, the solution would be to
|
||||||
|
wrap it in an object, since the forked process can hold a reference to that object
|
||||||
|
and ensure that the variable stays alive this way:
|
||||||
|
|
||||||
|
.. code-block:: sv
|
||||||
|
:linenos:
|
||||||
|
:emphasize-lines: 2q
|
||||||
|
|
||||||
|
class Wrapper;
|
||||||
|
int m_var;
|
||||||
|
|
||||||
|
// Here we implicitly hold a reference to `this`
|
||||||
|
task foo();
|
||||||
|
fork
|
||||||
|
#10 m_var++;
|
||||||
|
#20 $display("this.m_var = %d", m_var);
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// Here we explicitly hold a handle to an object
|
||||||
|
task bar(Wrapper wrapper);
|
||||||
|
fork
|
||||||
|
#10 wrapper.m_var++;
|
||||||
|
#20 $display("wrapper.m_var = %d", wrapper.m_var);
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
.. option:: LITENDIAN
|
.. option:: LITENDIAN
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ set(HEADERS
|
||||||
V3File.h
|
V3File.h
|
||||||
V3FileLine.h
|
V3FileLine.h
|
||||||
V3Force.h
|
V3Force.h
|
||||||
|
V3Fork.h
|
||||||
V3FunctionTraits.h
|
V3FunctionTraits.h
|
||||||
V3Gate.h
|
V3Gate.h
|
||||||
V3Global.h
|
V3Global.h
|
||||||
|
|
@ -227,6 +228,7 @@ set(COMMON_SOURCES
|
||||||
V3File.cpp
|
V3File.cpp
|
||||||
V3FileLine.cpp
|
V3FileLine.cpp
|
||||||
V3Force.cpp
|
V3Force.cpp
|
||||||
|
V3Fork.cpp
|
||||||
V3Gate.cpp
|
V3Gate.cpp
|
||||||
V3Global.cpp
|
V3Global.cpp
|
||||||
V3Graph.cpp
|
V3Graph.cpp
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@ RAW_OBJS = \
|
||||||
V3File.o \
|
V3File.o \
|
||||||
V3FileLine.o \
|
V3FileLine.o \
|
||||||
V3Force.o \
|
V3Force.o \
|
||||||
|
V3Fork.o \
|
||||||
V3Gate.o \
|
V3Gate.o \
|
||||||
V3Global.o \
|
V3Global.o \
|
||||||
V3Graph.o \
|
V3Graph.o \
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ public:
|
||||||
I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/
|
I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/
|
||||||
I_UNUSED, // Unused genvar, parameter or signal message (Backward Compatibility)
|
I_UNUSED, // Unused genvar, parameter or signal message (Backward Compatibility)
|
||||||
// Error codes:
|
// Error codes:
|
||||||
|
E_LIFETIME, // Error: Reference to a variable might outlive the variable.
|
||||||
E_NEEDTIMINGOPT, // Error: --timing/--no-timing option not specified
|
E_NEEDTIMINGOPT, // Error: --timing/--no-timing option not specified
|
||||||
E_NOTIMING, // Timing control encountered with --no-timing
|
E_NOTIMING, // Timing control encountered with --no-timing
|
||||||
E_PORTSHORT, // Error: Output port is connected to a constant, electrical short
|
E_PORTSHORT, // Error: Output port is connected to a constant, electrical short
|
||||||
|
|
@ -183,7 +184,7 @@ public:
|
||||||
// Boolean
|
// Boolean
|
||||||
" I_CELLDEFINE", " I_COVERAGE", " I_DEF_NETTYPE_WIRE", " I_LINT", " I_TIMING", " I_TRACING", " I_UNUSED",
|
" I_CELLDEFINE", " I_COVERAGE", " I_DEF_NETTYPE_WIRE", " I_LINT", " I_TIMING", " I_TRACING", " I_UNUSED",
|
||||||
// Errors
|
// Errors
|
||||||
"NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
|
"LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
|
||||||
// Warnings
|
// Warnings
|
||||||
" EC_FIRST_WARN",
|
" EC_FIRST_WARN",
|
||||||
"ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",
|
"ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||||
|
//*************************************************************************
|
||||||
|
// DESCRIPTION: Verilator: Create separate tasks for forked processes that
|
||||||
|
// can outlive their parents
|
||||||
|
//
|
||||||
|
// Code available from: https://verilator.org
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
//
|
||||||
|
// Copyright 2003-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
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
// V3Fork's Transformations:
|
||||||
|
//
|
||||||
|
// Each module:
|
||||||
|
// Look for FORKs [JOIN_NONE]/[JOIN_ANY]
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
|
||||||
|
#include "config_build.h"
|
||||||
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
#include "V3Fork.h"
|
||||||
|
|
||||||
|
#include "V3Ast.h"
|
||||||
|
#include "V3AstNodeExpr.h"
|
||||||
|
#include "V3Global.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
|
|
||||||
|
//######################################################################
|
||||||
|
// Fork visitor, transforms asynchronous blocks into separate tasks
|
||||||
|
|
||||||
|
class ForkVisitor final : public VNVisitor {
|
||||||
|
private:
|
||||||
|
// NODE STATE
|
||||||
|
// AstNode::user1() -> bool, 1 = Node was created as a call to an asynchronous task
|
||||||
|
// 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; // Class/module we are currently under
|
||||||
|
int m_forkDepth = 0; // Nesting level of asynchronous forks
|
||||||
|
bool m_newProcess = false; // True if we are directly under an asynchronous fork.
|
||||||
|
AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables
|
||||||
|
std::set<AstVar*> 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* varp = nullptr;
|
||||||
|
for (varp = m_capturedVarsp; varp; varp = VN_AS(varp->nextp(), Var))
|
||||||
|
if (varp->name() == refp->name()) break;
|
||||||
|
if (!varp) {
|
||||||
|
// Create a local copy for a capture
|
||||||
|
varp = new AstVar{refp->fileline(), VVarType::BLOCKTEMP, refp->name(), refp->dtypep()};
|
||||||
|
varp->direction(VDirection::INPUT);
|
||||||
|
varp->funcLocal(true);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return varp;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, std::string name) {
|
||||||
|
stmtsp = AstNode::addNext(static_cast<AstNode*>(m_capturedVarsp), stmtsp);
|
||||||
|
AstTask* const taskp = new AstTask{fl, name, stmtsp};
|
||||||
|
++m_createdTasksCount;
|
||||||
|
return taskp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string generateTaskName(AstNode* fromp, std::string kind) {
|
||||||
|
// TODO: Ensure no collisions occur
|
||||||
|
return "__V" + kind + (!fromp->name().empty() ? (fromp->name() + "__") : "UNNAMED__")
|
||||||
|
+ cvtToHex(fromp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void visitTaskifiable(AstNode* nodep) {
|
||||||
|
if (!m_newProcess || nodep->user1()) {
|
||||||
|
VL_RESTORER(m_forkDepth);
|
||||||
|
if (nodep->user1()) {
|
||||||
|
UASSERT(m_forkDepth > 0, "Wrong fork depth!");
|
||||||
|
--m_forkDepth;
|
||||||
|
}
|
||||||
|
iterateChildren(nodep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VL_RESTORER(m_capturedVarsp);
|
||||||
|
VL_RESTORER(m_capturedVarRefsp);
|
||||||
|
VL_RESTORER(m_newProcess);
|
||||||
|
m_capturedVarsp = nullptr;
|
||||||
|
m_capturedVarRefsp = nullptr;
|
||||||
|
m_newProcess = false;
|
||||||
|
|
||||||
|
iterateChildren(nodep);
|
||||||
|
|
||||||
|
// If there are no captures, there's no need to taskify
|
||||||
|
if (m_forkLocalsp.empty() && (m_capturedVarsp == nullptr) && !v3Global.opt.fTaskifyAll())
|
||||||
|
return;
|
||||||
|
|
||||||
|
VNRelinker handle;
|
||||||
|
AstTask* taskp = nullptr;
|
||||||
|
|
||||||
|
if (AstBegin* beginp = VN_CAST(nodep, Begin)) {
|
||||||
|
UASSERT(beginp->stmtsp(), "No stmtsp\n");
|
||||||
|
const std::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_");
|
||||||
|
taskp = makeTask(stmtp->fileline(), stmtp->unlinkFrBack(&handle), taskName);
|
||||||
|
} else if (AstFork* forkp = VN_CAST(nodep, Fork)) {
|
||||||
|
const std::string taskName = generateTaskName(forkp, "__FORK_NESTED_");
|
||||||
|
taskp = makeTask(forkp->fileline(), forkp->unlinkFrBack(&handle), taskName);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_modp->addStmtsp(taskp);
|
||||||
|
|
||||||
|
AstTaskRef* const taskrefp
|
||||||
|
= new AstTaskRef{nodep->fileline(), taskp->name(), m_capturedVarRefsp};
|
||||||
|
AstStmtExpr* const taskcallp = new AstStmtExpr{nodep->fileline(), taskrefp};
|
||||||
|
// Replaced nodes will be revisited, so we don't need to "lift" the arguments
|
||||||
|
// as captures in case of nested forks.
|
||||||
|
handle.relink(taskcallp);
|
||||||
|
taskcallp->user1(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VISITORS
|
||||||
|
void visit(AstFork* nodep) override {
|
||||||
|
bool nested = m_newProcess;
|
||||||
|
|
||||||
|
VL_RESTORER(m_forkLocalsp);
|
||||||
|
VL_RESTORER(m_newProcess);
|
||||||
|
VL_RESTORER(m_forkDepth)
|
||||||
|
if (!nodep->joinType().join()) {
|
||||||
|
++m_forkDepth;
|
||||||
|
m_newProcess = true;
|
||||||
|
m_forkLocalsp.clear();
|
||||||
|
// Nested forks get moved into separate tasks
|
||||||
|
if (nested) {
|
||||||
|
visitTaskifiable(nodep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_newProcess = false;
|
||||||
|
}
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
|
void visit(AstBegin* nodep) override { visitTaskifiable(nodep); }
|
||||||
|
void visit(AstNodeStmt* nodep) override { visitTaskifiable(nodep); }
|
||||||
|
void visit(AstVar* nodep) override {
|
||||||
|
if (m_forkDepth) m_forkLocalsp.insert(nodep);
|
||||||
|
}
|
||||||
|
void visit(AstVarRef* nodep) override {
|
||||||
|
|
||||||
|
// VL_KEEP_THIS ensures that we hold a handle to the class
|
||||||
|
if (m_forkDepth && !nodep->varp()->isFuncLocal() && nodep->varp()->isClassMember()) return;
|
||||||
|
|
||||||
|
if (m_forkDepth && (m_forkLocalsp.count(nodep->varp()) == 0)
|
||||||
|
&& !nodep->varp()->lifetime().isStatic()) {
|
||||||
|
if (nodep->access().isWriteOrRW()
|
||||||
|
&& (!nodep->isClassHandleValue() || nodep->user2())) {
|
||||||
|
nodep->v3warn(
|
||||||
|
E_LIFETIME,
|
||||||
|
"Invalid reference: Process might outlive variable `"
|
||||||
|
<< nodep->varp()->name() << "`.\n"
|
||||||
|
<< nodep->varp()->warnMore()
|
||||||
|
<< "... 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.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UASSERT_OBJ(
|
||||||
|
!nodep->varp()->lifetime().isNone(), nodep,
|
||||||
|
"Variable's lifetime is unknown. Can't determine if a capture is necessary.");
|
||||||
|
|
||||||
|
AstVar* const varp = captureRef(nodep);
|
||||||
|
nodep->varp(varp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void visit(AstAssign* nodep) override {
|
||||||
|
if (VN_IS(nodep->lhsp(), VarRef) && nodep->lhsp()->isClassHandleValue()) {
|
||||||
|
nodep->lhsp()->user2(true);
|
||||||
|
}
|
||||||
|
visit(static_cast<AstNodeStmt*>(nodep));
|
||||||
|
}
|
||||||
|
void visit(AstThisRef* nodep) override { return; }
|
||||||
|
void visit(AstNodeModule* nodep) override {
|
||||||
|
VL_RESTORER(m_modp);
|
||||||
|
m_modp = nodep;
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
|
void visit(AstNode* nodep) override {
|
||||||
|
VL_RESTORER(m_newProcess)
|
||||||
|
VL_RESTORER(m_forkDepth);
|
||||||
|
if (nodep->user1()) --m_forkDepth;
|
||||||
|
m_newProcess = false;
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||||
|
{
|
||||||
|
ForkVisitor fork_visitor(nodep);
|
||||||
|
createdTasksCount = fork_visitor.createdTasksCount();
|
||||||
|
}
|
||||||
|
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
|
||||||
|
|
||||||
|
return createdTasksCount;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||||
|
//*************************************************************************
|
||||||
|
// DESCRIPTION: Verilator: Create separate tasks for forked processes that
|
||||||
|
// can outlive their parents
|
||||||
|
//
|
||||||
|
// Code available from: https://verilator.org
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
//
|
||||||
|
// Copyright 2003-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
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
|
||||||
|
#ifndef VERILATOR_V3FORK_H_
|
||||||
|
#define VERILATOR_V3FORK_H_
|
||||||
|
|
||||||
|
#include "config_build.h"
|
||||||
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
class AstNetlist;
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
class V3Fork final {
|
||||||
|
public:
|
||||||
|
// Create tasks out of begin blocks that can outlive processes in which they were forked.
|
||||||
|
// Return value: number of tasks created
|
||||||
|
static int makeTasks(AstNetlist* nodep);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // Guard
|
||||||
|
|
@ -1224,6 +1224,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
||||||
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
|
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
|
||||||
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
|
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
|
||||||
DECL_OPTION("-ftable", FOnOff, &m_fTable);
|
DECL_OPTION("-ftable", FOnOff, &m_fTable);
|
||||||
|
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
|
||||||
|
|
||||||
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
|
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
|
||||||
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
|
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,7 @@ private:
|
||||||
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
|
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
|
||||||
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
||||||
bool m_fTable; // main switch: -fno-table: lookup table creation
|
bool m_fTable; // main switch: -fno-table: lookup table creation
|
||||||
|
bool m_fTaskifyAll = false; // main switch: --ftaskify-all-forked
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
bool m_available = false; // Set to true at the end of option parsing
|
bool m_available = false; // Set to true at the end of option parsing
|
||||||
|
|
@ -633,6 +634,7 @@ public:
|
||||||
bool fSubst() const { return m_fSubst; }
|
bool fSubst() const { return m_fSubst; }
|
||||||
bool fSubstConst() const { return m_fSubstConst; }
|
bool fSubstConst() const { return m_fSubstConst; }
|
||||||
bool fTable() const { return m_fTable; }
|
bool fTable() const { return m_fTable; }
|
||||||
|
bool fTaskifyAll() const { return m_fTaskifyAll; }
|
||||||
|
|
||||||
string traceClassBase() const { return m_traceFormat.classBase(); }
|
string traceClassBase() const { return m_traceFormat.classBase(); }
|
||||||
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
||||||
|
|
|
||||||
|
|
@ -299,17 +299,6 @@ void transformForks(AstNetlist* const netlistp) {
|
||||||
// Not func local, or not declared before the fork. Their lifetime is longer
|
// Not func local, or not declared before the fork. Their lifetime is longer
|
||||||
// than the forked process. Skip
|
// than the forked process. Skip
|
||||||
return;
|
return;
|
||||||
} else if (m_forkp->joinType().join()) {
|
|
||||||
// If it's fork..join, we can refer to variables from the parent process
|
|
||||||
} else {
|
|
||||||
// TODO: It is possible to relax this by allowing the use of such variables up
|
|
||||||
// until the first await. Also, variables defined within a forked process
|
|
||||||
// (inside a begin) are extracted out by V3Begin, so they also trigger this
|
|
||||||
// error. Preventing this (or detecting such cases and moving the vars back)
|
|
||||||
// would also allow for using them freely.
|
|
||||||
refp->v3warn(E_UNSUPPORTED, "Unsupported: variable local to a forking process "
|
|
||||||
"accessed in a fork..join_any or fork..join_none");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Remap the reference
|
// Remap the reference
|
||||||
AstVarScope* const vscp = refp->varScopep();
|
AstVarScope* const vscp = refp->varScopep();
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
#include "V3Expand.h"
|
#include "V3Expand.h"
|
||||||
#include "V3File.h"
|
#include "V3File.h"
|
||||||
#include "V3Force.h"
|
#include "V3Force.h"
|
||||||
|
#include "V3Fork.h"
|
||||||
#include "V3Gate.h"
|
#include "V3Gate.h"
|
||||||
#include "V3Global.h"
|
#include "V3Global.h"
|
||||||
#include "V3Graph.h"
|
#include "V3Graph.h"
|
||||||
|
|
@ -221,6 +222,10 @@ static void process() {
|
||||||
V3Inst::dearrayAll(v3Global.rootp());
|
V3Inst::dearrayAll(v3Global.rootp());
|
||||||
V3LinkDot::linkDotArrayed(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
|
||||||
|
|
||||||
// Task inlining & pushing BEGINs names to variables/cells
|
// Task inlining & pushing BEGINs names to variables/cells
|
||||||
// Begin processing must be after Param, before module inlining
|
// Begin processing must be after Param, before module inlining
|
||||||
V3Begin::debeginAll(v3Global.rootp()); // Flatten cell names, before inliner
|
V3Begin::debeginAll(v3Global.rootp()); // Flatten cell names, before inliner
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
%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,0 +1,21 @@
|
||||||
|
#!/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(linter => 1);
|
||||||
|
|
||||||
|
top_filename("t/t_clocking_sched.v");
|
||||||
|
|
||||||
|
lint(
|
||||||
|
verilator_flags2 => ["--timing", "--ftaskify-all-forked"],
|
||||||
|
expect_filename => $Self->{golden_filename},
|
||||||
|
fails => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 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 => ["--timing"],
|
||||||
|
);
|
||||||
|
|
||||||
|
execute(
|
||||||
|
check_finished => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||||
|
// any use, without warranty, 2023 by Antmicro Ltd.
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
event evt1, evt2, evt3;
|
||||||
|
|
||||||
|
class Foo;
|
||||||
|
task do_something(int cap1, int cap2);
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
$display("outer fork: %d", cap1);
|
||||||
|
fork
|
||||||
|
$display("inner fork: %d", cap2);
|
||||||
|
->evt2;
|
||||||
|
fork
|
||||||
|
$display("innermost fork: %d", cap2);
|
||||||
|
->evt3;
|
||||||
|
join_none
|
||||||
|
join_none
|
||||||
|
end
|
||||||
|
->evt1;
|
||||||
|
join_any
|
||||||
|
endtask
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t();
|
||||||
|
reg a, b, c;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
Foo foo;
|
||||||
|
|
||||||
|
a = 1'b0;
|
||||||
|
b = 1'b0;
|
||||||
|
foo = new;
|
||||||
|
foo.do_something(1, 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(evt1) begin
|
||||||
|
a <= 1;
|
||||||
|
end
|
||||||
|
always @(evt2) begin
|
||||||
|
b <= 1;
|
||||||
|
end
|
||||||
|
always @(evt3) begin
|
||||||
|
c <= 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(a, b, c) begin
|
||||||
|
if (a & b & c) begin
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||||
|
// any use, without warranty, 2023 by Antmicro Ltd.
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
event evt1;
|
||||||
|
|
||||||
|
class Foo;
|
||||||
|
int m_member;
|
||||||
|
|
||||||
|
task do_something();
|
||||||
|
fork
|
||||||
|
#20 begin
|
||||||
|
m_member++;
|
||||||
|
$display("this's m_member: ", m_member);
|
||||||
|
if (m_member != 3)
|
||||||
|
$stop;
|
||||||
|
->evt1;
|
||||||
|
end
|
||||||
|
#10 begin
|
||||||
|
m_member++;
|
||||||
|
bar(this);
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
|
static task bar(Foo foo);
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
foo.m_member++;
|
||||||
|
$display("foo's m_member: %d", foo.m_member);
|
||||||
|
if (foo.m_member != 2)
|
||||||
|
$stop;
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t();
|
||||||
|
initial begin
|
||||||
|
Foo foo;
|
||||||
|
foo = new;
|
||||||
|
foo.m_member = 0;
|
||||||
|
foo.do_something();
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(evt1) begin
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
[0] fork..join process 4
|
||||||
|
[2] fork..join process 3
|
||||||
|
[4] fork..join process 2
|
||||||
|
[8] fork..join process 1
|
||||||
|
[16] fork in fork starts
|
||||||
|
[16] fork..join process 8
|
||||||
|
[20] fork..join process 7
|
||||||
|
[24] fork..join process 6
|
||||||
|
[32] fork..join process 5
|
||||||
|
[32] fork..join in fork ends
|
||||||
|
[64] main process
|
||||||
|
fork..join_any process 2
|
||||||
|
back in main process
|
||||||
|
fork..join_any process 1
|
||||||
|
fork..join_any process 1
|
||||||
|
back in main process
|
||||||
|
fork..join_any process 2
|
||||||
|
in main process
|
||||||
|
fork..join_none process 1
|
||||||
|
fork..join_none process 2
|
||||||
|
fork..join_none process 3
|
||||||
|
fork..join_none process 2 again
|
||||||
|
fork..join_none process 1 again
|
||||||
|
fork..join_none process 3 again
|
||||||
|
*-* All Finished *-*
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 2022 by Wilson Snyder. This program is free software; you
|
||||||
|
# can redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
# Version 2.0.
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
|
top_filename("t/t_timing_fork_join.v");
|
||||||
|
|
||||||
|
compile(
|
||||||
|
verilator_flags2 => ["--exe --main --timing --ftaskify-all-forked"],
|
||||||
|
make_main => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
execute(
|
||||||
|
check_finished => 1,
|
||||||
|
expect_filename => $Self->{golden_filename},
|
||||||
|
);
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -1,34 +1,17 @@
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:12:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
%Error-LIFETIME: t/t_timing_fork_unsup.v:12:12: Invalid reference: Process might outlive variable `x`.
|
||||||
: ... In instance t::C
|
: ... 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;
|
12 | #6 x = 4;
|
||||||
| ^
|
| ^
|
||||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
... For error description see https://verilator.org/warn/LIFETIME?v=latest
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:13:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
%Error-LIFETIME: t/t_timing_fork_unsup.v:13:12: Invalid reference: Process might outlive variable `x`.
|
||||||
: ... In instance t::C
|
: ... 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++;
|
13 | #2 x++;
|
||||||
| ^
|
| ^
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:16: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
%Error-LIFETIME: t/t_timing_fork_unsup.v:14:9: Invalid reference: Process might outlive variable `x`.
|
||||||
: ... In instance t::C
|
: ... In instance t
|
||||||
14 | x = #4 x * 3;
|
: ... 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.
|
||||||
| ^
|
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:9: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
|
||||||
: ... In instance t::C
|
|
||||||
14 | x = #4 x * 3;
|
14 | x = #4 x * 3;
|
||||||
| ^
|
| ^
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:16:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
|
||||||
: ... In instance t::C
|
|
||||||
16 | #1 if (x != 1) $stop;
|
|
||||||
| ^
|
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:17:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
|
||||||
: ... In instance t::C
|
|
||||||
17 | #2 if (x != 2) $stop;
|
|
||||||
| ^
|
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:18:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
|
||||||
: ... In instance t::C
|
|
||||||
18 | #2 if (x != 3) $stop;
|
|
||||||
| ^
|
|
||||||
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:19:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
|
|
||||||
: ... In instance t::C
|
|
||||||
19 | #2 if (x != 4) $stop;
|
|
||||||
| ^
|
|
||||||
%Error: Exiting due to
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,6 @@ module t;
|
||||||
#6 x = 4;
|
#6 x = 4;
|
||||||
#2 x++;
|
#2 x++;
|
||||||
x = #4 x * 3;
|
x = #4 x * 3;
|
||||||
begin
|
|
||||||
#1 if (x != 1) $stop;
|
|
||||||
#2 if (x != 2) $stop;
|
|
||||||
#2 if (x != 3) $stop;
|
|
||||||
#2 if (x != 4) $stop;
|
|
||||||
$finish;
|
|
||||||
end
|
|
||||||
join_none
|
join_none
|
||||||
x = 1;
|
x = 1;
|
||||||
endtask
|
endtask
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 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 => ["--timing"],
|
||||||
|
);
|
||||||
|
|
||||||
|
execute(
|
||||||
|
check_finished => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
int static_var;
|
||||||
|
|
||||||
|
module t();
|
||||||
|
event evt;
|
||||||
|
task send_event();
|
||||||
|
->evt;
|
||||||
|
endtask
|
||||||
|
|
||||||
|
|
||||||
|
class Foo;
|
||||||
|
function void do_something(int captured_var);
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
int my_var;
|
||||||
|
int my_other_var;
|
||||||
|
my_var = captured_var;
|
||||||
|
my_other_var = captured_var; /* Capture the same value "twice" */
|
||||||
|
my_var++;
|
||||||
|
static_var++; /* Write to a value with static lifetime (valid) */
|
||||||
|
$display("Vars in forked process: %d %d", my_var, my_other_var);
|
||||||
|
if (my_var != 2)
|
||||||
|
$stop;
|
||||||
|
if (my_other_var != 1)
|
||||||
|
$stop;
|
||||||
|
send_event();
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
$display("Leaving fork's parent");
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
Foo foo;
|
||||||
|
foo = new;
|
||||||
|
static_var = 0;
|
||||||
|
foo.do_something(1);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(evt) begin
|
||||||
|
$display("Static variable: %d", static_var);
|
||||||
|
if (static_var != 1)
|
||||||
|
$stop;
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
Loading…
Reference in New Issue