Support disable fork (#4125) (#4569)

This commit is contained in:
Aleksander Kiryk 2023-10-16 14:02:29 +02:00 committed by GitHub
parent 25dde58297
commit ad3bcbb1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 441 additions and 87 deletions

View File

@ -81,11 +81,16 @@ constexpr IData VL_CLOG2_CE_Q(QData lhs) VL_PURE {
return lhs <= 1 ? 0 : VL_CLOG2_CE_Q((lhs + 1) >> 1ULL) + 1;
}
//===================================================================
// VlProcess stores metadata of running processes
// Metadata of processes
class VlProcess;
using VlProcessRef = std::shared_ptr<VlProcess>;
class VlProcess final {
// MEMBERS
int m_state; // Current state of the process
VlProcessRef m_parentp = nullptr; // Parent process, if exists
std::set<VlProcess*> m_children; // Active child processes
public:
// TYPES
@ -98,16 +103,34 @@ public:
};
// CONSTRUCTORS
// Construct independent process
VlProcess()
: m_state{RUNNING} {}
// Construct child process of parent
VlProcess(VlProcessRef parentp)
: m_state{RUNNING}
, m_parentp{parentp} {
m_parentp->attach(this);
}
~VlProcess() {
if (m_parentp) m_parentp->detach(this);
}
void attach(VlProcess* childp) { m_children.insert(childp); }
void detach(VlProcess* childp) { m_children.erase(childp); }
// METHODS
int state() { return m_state; }
void state(int s) { m_state = s; }
void disable() {
state(KILLED);
disable_fork();
}
void disable_fork() {
for (VlProcess* childp : m_children) childp->disable();
}
};
using VlProcessRef = std::shared_ptr<VlProcess>;
inline std::string VL_TO_STRING(const VlProcessRef& p) { return std::string("process"); }
//===================================================================

View File

@ -86,7 +86,7 @@ private:
bool m_recursive : 1; // Recursive or part of recursion
bool m_underGenerate : 1; // Under generate (for warning)
bool m_virtual : 1; // Virtual method in class
bool m_needProcess : 1; // Implements part of a process that allocates std::process
bool m_needProcess : 1; // Needs access to VlProcess of the caller
VLifetime m_lifetime; // Lifetime
VIsCached m_purity; // Pure state
@ -300,7 +300,7 @@ class AstNodeProcedure VL_NOT_FINAL : public AstNode {
// IEEE procedure: initial, final, always
// @astgen op2 := stmtsp : List[AstNode] // Note: op1 is used in some sub-types only
bool m_suspendable : 1; // Is suspendable by a Delay, EventControl, etc.
bool m_needProcess : 1; // Implements part of a process that allocates std::process
bool m_needProcess : 1; // Uses VlProcess
protected:
AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp)
: AstNode{t, fl} {
@ -608,7 +608,7 @@ private:
bool m_dpiImportPrototype : 1; // This is the DPI import prototype (i.e.: provided by user)
bool m_dpiImportWrapper : 1; // Wrapper for invoking DPI import prototype from generated code
bool m_dpiTraceInit : 1; // DPI trace_init
bool m_needProcess : 1; // Implements part of a process that allocates std::process
bool m_needProcess : 1; // Needs access to VlProcess of the caller
public:
AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType = "")
: ASTGEN_SUPER_CFunc(fl) {
@ -2096,19 +2096,23 @@ class AstBegin final : public AstNodeBlock {
// Parents: statement
// @astgen op1 := genforp : Optional[AstNode]
bool m_generate; // Underneath a generate
const bool m_implied; // Not inserted by user
bool m_generate : 1; // Underneath a generate
bool m_needProcess : 1; // Uses VlProcess
const bool m_implied : 1; // Not inserted by user
public:
// Node that puts name into the output stream
AstBegin(FileLine* fl, const string& name, AstNode* stmtsp, bool generate = false,
bool implied = false)
: ASTGEN_SUPER_Begin(fl, name, stmtsp)
, m_generate{generate}
, m_needProcess{false}
, m_implied{implied} {}
ASTGEN_MEMBERS_AstBegin;
void dump(std::ostream& str) const override;
void generate(bool flag) { m_generate = flag; }
bool generate() const { return m_generate; }
void setNeedProcess() { m_needProcess = true; }
bool needProcess() const { return m_needProcess; }
bool implied() const { return m_implied; }
};
class AstFork final : public AstNodeBlock {

View File

@ -2321,6 +2321,7 @@ void AstBegin::dump(std::ostream& str) const {
if (generate()) str << " [GEN]";
if (genforp()) str << " [GENFOR]";
if (implied()) str << " [IMPLIED]";
if (needProcess()) str << " [NPRC]";
}
void AstCoverDecl::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);

View File

@ -410,7 +410,8 @@ void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const stri
displayEmit(nodep, isScan);
}
void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer) {
void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer,
bool inProcess) {
puts("(");
bool comma = false;
if (nodep->funcp()->isLoose() && !nodep->funcp()->isStatic()) {
@ -422,6 +423,8 @@ void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPoint
if (comma) puts(", ");
if (VN_IS(nodep->backp(), CAwait) || !nodep->funcp()->isCoroutine()) {
puts("vlProcess");
} else if (inProcess) {
puts("std::make_shared<VlProcess>(vlProcess)");
} else {
puts("std::make_shared<VlProcess>()");
}

View File

@ -205,7 +205,7 @@ public:
}
void emitOpName(AstNode* nodep, const string& format, AstNode* lhsp, AstNode* rhsp,
AstNode* thsp);
void emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer);
void emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer, bool inProcess);
void emitDereference(const string& pointer);
void emitCvtPackStr(AstNode* nodep);
void emitCvtWideArray(AstNode* nodep, AstNode* fromp);
@ -500,7 +500,7 @@ public:
}
puts(funcp->nameProtect());
}
emitCCallArgs(nodep, nodep->selfPointerProtect(m_useSelfForThis));
emitCCallArgs(nodep, nodep->selfPointerProtect(m_useSelfForThis), m_cfuncp->needProcess());
}
void visit(AstCMethodCall* nodep) override {
const AstCFunc* const funcp = nodep->funcp();
@ -508,7 +508,7 @@ public:
iterateConst(nodep->fromp());
putbs("->");
puts(funcp->nameProtect());
emitCCallArgs(nodep, "");
emitCCallArgs(nodep, "", m_cfuncp->needProcess());
}
void visit(AstCAwait* nodep) override {
puts("co_await ");
@ -613,6 +613,7 @@ public:
puts("]);\n");
}
}
void visit(AstDisableFork* nodep) override { puts("vlProcess->disable_fork();\n"); }
void visit(AstCReturn* nodep) override {
puts("return (");
iterateAndNextConstNull(nodep->lhsp());

View File

@ -330,7 +330,7 @@ private:
AstJumpLabel* const labelp = findAddLabel(beginp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), labelp});
} else {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disable fork");
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork by name");
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);

View File

@ -279,7 +279,6 @@ void transformForks(AstNetlist* const netlistp) {
// STATE
bool m_inClass = false; // Are we in a class?
bool m_beginHasAwaits = false; // Does the current begin have awaits?
bool m_beginNeedProcess = false; // Does the current begin have process::self dependency?
AstFork* m_forkp = nullptr; // Current fork
AstCFunc* m_funcp = nullptr; // Current function
@ -358,9 +357,8 @@ void transformForks(AstNetlist* const netlistp) {
UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork");
// Start with children, so later we only find awaits that are actually in this begin
m_beginHasAwaits = false;
m_beginNeedProcess = false;
iterateChildrenConst(nodep);
if (m_beginHasAwaits || m_beginNeedProcess) {
if (m_beginHasAwaits || nodep->needProcess()) {
UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
// Create a function to put this begin's statements in
FileLine* const flp = nodep->fileline();
@ -384,7 +382,7 @@ void transformForks(AstNetlist* const netlistp) {
}
// Put the begin's statements in the function, delete the begin
newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
if (m_beginNeedProcess) {
if (nodep->needProcess()) {
newfuncp->setNeedProcess();
newfuncp->addStmtsp(new AstCStmt{nodep->fileline(),
"vlProcess->state(VlProcess::FINISHED);\n"});
@ -405,10 +403,6 @@ void transformForks(AstNetlist* const netlistp) {
m_beginHasAwaits = true;
iterateChildrenConst(nodep);
}
void visit(AstCCall* nodep) override {
if (nodep->funcp()->needProcess()) m_beginNeedProcess = true;
iterateChildrenConst(nodep);
}
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
//--------------------

View File

@ -12,11 +12,27 @@
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
// TimingSuspendableVisitor locates all C++ functions and processes that contain timing controls,
// and marks them as suspendable. If a process calls a suspendable function, then it is also marked
// as suspendable. If a function calls or overrides a suspendable function, it is also marked as
// suspendable. TimingSuspendableVisitor creates a dependency graph to propagate this property. It
// does not perform any AST transformations.
// TimingSuspendableVisitor does not perform any AST transformations.
// Instead it propagates two types of flags:
// - flag "suspendable": (for detecting what will need to become a coroutine)
// The visitor locates all C++ functions and processes that contain timing controls,
// and marks them as suspendable. If a process calls a suspendable function,
// then it is also marked as suspendable. If a function calls or overrides
// a suspendable function, it is also marked as suspendable.
// TimingSuspendableVisitor creates a dependency graph to propagate this property.
// - flag "needs process": (for detecting what needs a VlProcess argument in signature)
// The visitor distinguishes 4 types of nodes:
// - T_ALLOCS_PROC: nodes that can allocate VlProcess (forks, always, initial etc.),
// - T_FORCES_PROC: nodes that make it necessary for the previous type to allocate VlProcess
// (like process::self which then wraps it, allowing use inside Verilog).
// - T_NEEDS_PROC: nodes that should obtain VlProcess if it will be allocated
// (all of the previous type + timing controls, so they could update process state),
// - T_HAS_PROC: nodes that are going to be emitted with a VlProcess argument.
// T_FORCES_PROC and T_NEEDS_PROC are propagated upwards up to the nodes of type T_ALLOCS_PROC,
// this is to detect which processes have to allocate VlProcess. Then nodes of these processes
// get marked as T_HAS_PROC and the flag is propagated downwards through nodes
// type T_NEEDS_PROC. Using only nodes type T_NEEDS_PROC assures the flags are only propagated
// through paths leading to nodes that actually use VlProcess.
//
// TimingControlVisitor is the one that actually performs transformations:
// - for each intra-assignment timing control:
@ -70,8 +86,10 @@ VL_DEFINE_DEBUG_FUNCTIONS;
enum NodeFlag : uint8_t {
T_SUSPENDEE = 1 << 0, // Suspendable (due to dependence on another suspendable)
T_SUSPENDER = 1 << 1, // Suspendable (has timing control)
T_HAS_PROC = 1 << 2, // Has an associated std::process
T_CALLS_PROC_SELF = 1 << 3, // Calls std::process::self
T_ALLOCS_PROC = 1 << 2, // Can allocate VlProcess
T_FORCES_PROC = 1 << 3, // Forces VlProcess allocation
T_NEEDS_PROC = 1 << 4, // Needs access to VlProcess if it's allocated
T_HAS_PROC = 1 << 5, // Has VlProcess argument in the signature
};
enum ForkType : uint8_t {
@ -86,6 +104,11 @@ enum PropagationType : uint8_t {
P_SIGNATURE = 3, // Propagation required to maintain C++ function's signature requirements
};
// Add timing flag to a node
static void addFlags(AstNode* const nodep, uint8_t flags) { nodep->user2(nodep->user2() | flags); }
// Check if a node has ALL of the expected flags set
static bool hasFlags(AstNode* const nodep, uint8_t flags) { return !(~nodep->user2() & flags); }
// ######################################################################
// Detect nodes affected by timing and/or requiring a process
@ -126,8 +149,8 @@ private:
class SuspendDepVtx final : public DepVtx {
VL_RTTI_IMPL(SuspendDepVtx, DepVtx)
string dotColor() const override {
if (nodep()->user2() & T_SUSPENDER) return "red";
if (nodep()->user2() & T_SUSPENDEE) return "blue";
if (hasFlags(nodep(), T_SUSPENDER)) return "red";
if (hasFlags(nodep(), T_SUSPENDEE)) return "blue";
return "black";
}
@ -140,8 +163,9 @@ private:
class NeedsProcDepVtx final : public DepVtx {
VL_RTTI_IMPL(NeedsProcDepVtx, DepVtx)
string dotColor() const override {
if (nodep()->user2() & T_CALLS_PROC_SELF) return "red";
if (nodep()->user2() & T_HAS_PROC) return "blue";
if (hasFlags(nodep(), T_HAS_PROC)) return "blue";
if (hasFlags(nodep(), T_NEEDS_PROC)) return "green";
if (hasFlags(nodep(), T_FORCES_PROC)) return "red";
return "black";
}
@ -198,10 +222,10 @@ private:
if (!nodep->user5p()) nodep->user5p(new NeedsProcDepVtx{&m_procGraph, nodep, classp});
return nodep->user5u().to<NeedsProcDepVtx*>();
}
// Set timing flag of a node
// Pass timing flag between nodes
bool passFlag(const AstNode* from, AstNode* to, NodeFlag flag) {
if ((from->user2() & flag) && !(to->user2() & flag)) {
to->user2(to->user2() | flag);
addFlags(to, flag);
return true;
}
return false;
@ -216,6 +240,15 @@ private:
}
}
template <typename Predicate>
void propagateFlagsIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
auto* const parentp = vxp->nodep();
for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
auto* const depVxp = static_cast<DepVtx*>(edgep->top());
AstNode* const depp = depVxp->nodep();
if (p(edgep) && passFlag(parentp, depp, flag)) propagateFlagsIf(depVxp, flag, p);
}
}
template <typename Predicate>
void propagateFlagsReversedIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
auto* const parentp = vxp->nodep();
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
@ -236,17 +269,22 @@ private:
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
getNeedsProcDepVtx(nodep);
addFlags(nodep, T_ALLOCS_PROC);
if (VN_IS(nodep, Always)) {
UINFO(1, "Always does " << (nodep->needProcess() ? "" : "NOT ") << "need process\n");
}
iterateChildren(nodep);
}
void visit(AstDisableFork* nodep) override {
visit(static_cast<AstNode*>(nodep));
addFlags(m_procp, T_FORCES_PROC | T_NEEDS_PROC);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
if (nodep->needProcess()) addFlags(nodep, T_FORCES_PROC | T_NEEDS_PROC);
DepVtx* const sVxp = getSuspendDepVtx(nodep);
DepVtx* const pVxp = getNeedsProcDepVtx(nodep);
if (!m_classp) return;
@ -287,9 +325,12 @@ private:
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()),
getSuspendDepVtx(m_procp), m_underFork ? P_FORK : P_CALL};
if (!m_underFork)
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
getNeedsProcDepVtx(m_procp), P_CALL};
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
getNeedsProcDepVtx(m_procp), P_CALL};
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
}
iterateChildren(nodep);
}
@ -301,9 +342,12 @@ private:
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep), getSuspendDepVtx(m_procp),
m_underFork ? P_FORK : P_CALL};
if (!m_underFork)
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
P_CALL};
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
P_CALL};
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
}
m_procp = nodep;
m_underFork = 0;
@ -316,7 +360,7 @@ private:
// so that transformForks() in V3SchedTiming gets called and
// removes all forks and begins
if (nodep->isTimingControl() && m_procp) {
m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER);
m_underFork |= F_MIGHT_SUSPEND;
}
m_underFork |= F_MIGHT_NEED_PROC;
@ -325,7 +369,7 @@ private:
void visit(AstNode* nodep) override {
if (nodep->isTimingControl()) {
v3Global.setUsesTiming();
if (m_procp) m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
if (m_procp) addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER | T_NEEDS_PROC);
}
iterateChildren(nodep);
}
@ -342,22 +386,35 @@ public:
// Propagate suspendability
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_SUSPENDEE) propagateFlags(depVxp, T_SUSPENDEE);
if (hasFlags(depVxp->nodep(), T_SUSPENDEE)) propagateFlags(depVxp, T_SUSPENDEE);
}
if (dumpGraphLevel() >= 6) m_suspGraph.dumpDotFilePrefixed("timing_deps");
// Propagate process
// Propagate T_HAS_PROCESS
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_HAS_PROC) propagateFlags(depVxp, T_HAS_PROC);
}
// Propagate process downwards (from caller to callee) for suspendable calls
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_HAS_PROC)
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
return (e->weight() != P_FORK)
&& (static_cast<DepVtx*>(e->top())->nodep()->user2() & T_SUSPENDEE);
// Find processes that'll allocate VlProcess
if (hasFlags(depVxp->nodep(), T_FORCES_PROC)) {
propagateFlagsIf(depVxp, T_FORCES_PROC, [&](const V3GraphEdge* e) -> bool {
return !hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_ALLOCS_PROC);
});
}
// Mark nodes on paths between processes and statements that use VlProcess
if (hasFlags(depVxp->nodep(), T_NEEDS_PROC)) {
propagateFlagsIf(depVxp, T_NEEDS_PROC, [&](const V3GraphEdge* e) -> bool {
return !hasFlags(static_cast<DepVtx*>(e->top())->nodep(), T_ALLOCS_PROC);
});
}
}
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
// Mark nodes that will be emitted with a VlProcess argument
if (hasFlags(depVxp->nodep(), T_ALLOCS_PROC | T_FORCES_PROC)) {
addFlags(depVxp->nodep(), T_HAS_PROC);
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
return hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_NEEDS_PROC);
});
}
}
if (dumpGraphLevel() >= 6) m_procGraph.dumpDotFilePrefixed("proc_deps");
}
@ -586,7 +643,7 @@ private:
void addProcessInfo(AstCMethodHard* const methodp) const {
FileLine* const flp = methodp->fileline();
AstCExpr* const ap = new AstCExpr{
flp, m_procp && (m_procp->user2() & T_HAS_PROC) ? "vlProcess" : "nullptr", 0};
flp, m_procp && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr", 0};
ap->dtypeSetVoid();
methodp->addPinsp(ap);
}
@ -697,8 +754,8 @@ private:
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2() & T_SUSPENDEE) nodep->setSuspendable();
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (hasFlags(nodep, T_SUSPENDEE)) nodep->setSuspendable();
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
}
void visit(AstInitial* nodep) override {
visit(static_cast<AstNodeProcedure*>(nodep));
@ -719,11 +776,11 @@ private:
// Workaround for killing `always` processes (doing that is pretty much UB)
// TODO: Disallow killing `always` at runtime (throw an error)
if (nodep->user2() & T_HAS_PROC) nodep->user2(nodep->user2() | T_SUSPENDEE);
if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE);
iterateChildren(nodep);
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (!(nodep->user2() & T_SUSPENDEE)) return;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (!hasFlags(nodep, T_SUSPENDEE)) return;
nodep->setSuspendable();
FileLine* const flp = nodep->fileline();
AstSenTree* const sensesp = m_activep->sensesp();
@ -744,8 +801,8 @@ private:
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (!(nodep->user2() & T_SUSPENDEE)) return;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (!(hasFlags(nodep, T_SUSPENDEE))) return;
nodep->rtnType("VlCoroutine");
// If in a class, create a shared pointer to 'this'
@ -768,7 +825,7 @@ private:
}
}
void visit(AstNodeCCall* nodep) override {
if ((nodep->funcp()->user2() & T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep};
@ -1048,6 +1105,7 @@ private:
void visit(AstBegin* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
iterateChildren(nodep);
}
void visit(AstFork* nodep) override {
@ -1061,6 +1119,7 @@ private:
while (stmtp) {
if (!VN_IS(stmtp, Begin)) {
auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
if (hasFlags(stmtp, T_HAS_PROC)) addFlags(beginp, T_HAS_PROC);
stmtp->replaceWith(beginp);
beginp->addStmtsp(stmtp);
stmtp = beginp;

View File

@ -696,8 +696,14 @@ private:
}
}
void visit(AstDisableFork* nodep) override {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disable fork statements");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
if (nodep->fileline()->timingOn()) {
if (v3Global.opt.timing().isSetFalse()) {
nodep->v3warn(E_NOTIMING, "Support for disable fork statement requires --timing");
} else if (!v3Global.opt.timing().isSetTrue()) {
nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how "
<< "disable fork should be handled");
}
}
}
void visit(AstWaitFork* nodep) override {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: wait fork statements");

View File

@ -0,0 +1,5 @@
%Error-UNSUPPORTED: t/t_disable.v:11:10: Unsupported: disabling fork by name
11 | disable foo;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

20
test_regress/t/t_disable.pl Executable file
View File

@ -0,0 +1,20 @@
#!/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);
lint(
verilator_flags2 => ['--lint-only --timing'],
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -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, 2023 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t(/*AUTOARG*/);
initial begin
fork : foo
disable foo;
#1 $stop;
join_none
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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;

View File

@ -0,0 +1,49 @@
// 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
`define N 3
class Cls;
task runforks(integer n);
for (integer i = 0; i < n; i++) fork
#1 $stop;
join_none
endtask
endclass
module t;
Cls cls = new;
initial begin
// run forks
for (integer i = 0; i < `N; i++) fork
#1 $stop;
join_none
// run forks inside a method
cls.runforks(`N);
// run forks in forks
for (integer i = 0; i < `N; i++) fork
for (integer j = 0; j < `N; j++) fork
#1 $stop;
join_none
join_none
for (integer i = 0; i < `N; i++) fork
cls.runforks(`N);
join_none
// kill them all
disable fork;
// check if we can still fork
fork
#2 $write("*-* All Finished *-*\n");
#3 $finish;
join_none
end
endmodule

View File

@ -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;

View File

@ -0,0 +1,58 @@
// 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
// USING THIS FOR DEBUGGING PROCESS PROPAGATION:
//
// The example contains most cases that were problematic during the
// works on support of 'disable fork' statement, including:
//
// - indirect use of disable fork (through a task),
// - indirect use of forks that are to be disabled,
// - forks in forks,
// - a function taking VlProcess argument shared between a process that
// allocates VlProcess, and one that doesnt,
// - a function that has a delay and obtains VlProcess argument,
// - a function that has a delay and doesn't obtain it.
//
// Blocks below contain info on whether they should (YES) or shouldn't (NO)
// be emitted as functions with a VlProcess argument.
//
// To check if that corresponds to reality, see blue nodes in proc_deps.dot
class Cls;
task print; /*NO*/
$write("*-* All ");
endtask
task disable_fork_func; /*YES*/
disable fork;
endtask
task common_func; /*YES*/
fork /*YES*/ #1; join_none
endtask
task fork_func; /*YES*/
fork /*YES*/ #1 $stop; join_none
endtask
task delay_func; /*NO*/
fork /*NO*/ #1 $write("Finished *-*\n"); join_none
endtask
endclass
module t;
Cls cls = new;
initial begin /*YES*/
fork /*YES*/ cls.common_func(); join_none
cls.fork_func();
cls.disable_fork_func();
cls.print();
end
initial begin /*NO*/
cls.delay_func();
cls.common_func();
fork /*YES*/ disable fork; join_none
end
endmodule

View File

@ -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;

View File

@ -0,0 +1,18 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
initial begin
fork begin
fork begin
#3 $stop;
end join_none
#1;
end join_none
#2 disable fork;
end
initial #4 $write("*-* All Finished *-*\n");
endmodule

View File

@ -0,0 +1,6 @@
%Error-NOTIMING: t/t_disable_fork_notiming.v:8:12: Support for disable fork statement requires --timing
: ... note: In instance 't'
8 | initial disable fork;
| ^~~~~~~
... For error description see https://verilator.org/warn/NOTIMING?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/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(
expect_filename => $Self->{golden_filename},
v_flags2 => ["--no-timing"],
fails => 1,
);
ok(1);
1;

View File

@ -0,0 +1,9 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
initial disable fork;
endmodule

View File

@ -1,10 +0,0 @@
%Error-UNSUPPORTED: t/t_fork_disable.v:16:7: Unsupported: disable fork statements
: ... note: In instance 't'
16 | disable fork;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_fork_disable.v:17:7: Unsupported: wait fork statements
: ... note: In instance 't'
17 | wait fork;
| ^~~~
%Error: Exiting due to

View File

@ -11190,9 +11190,7 @@ task uvm_phase::execute_phase();
end
end
join_any
//TODO issue #4125 - Support disable fork
//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:11187:12: Unsupported: disable fork statements
//TODO disable fork;
disable fork;
end
join
end
@ -15172,9 +15170,7 @@ class uvm_heartbeat extends uvm_object;
end
@(m_stop_event);
join_any
//TODO issue #4125 - Support disable fork
//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:15167:12: Unsupported: disable fork statements
//TODO disable fork;
disable fork;
endtask
endclass
class uvm_heartbeat_callback extends uvm_objection_callback;
@ -19218,9 +19214,7 @@ task uvm_sequencer_base::m_wait_for_available_sequence();
join_any
end
join_any
//TODO issue #4125 - Support disable fork
//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:19213:12: Unsupported: disable fork statements
//TODO disable fork;
disable fork;
end
join
endtask

View File

@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_wait_fork.v:17:7: Unsupported: wait fork statements
: ... note: In instance 't'
17 | wait fork;
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to