Fix fork scheduling semantics (#6730)

Signed-off-by: Artur Bieniek <abieniek@internships.antmicro.com>
This commit is contained in:
Artur Bieniek 2025-11-26 13:52:53 +01:00 committed by GitHub
parent f4654a451b
commit 2c5ff3f63f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 401 additions and 63 deletions

View File

@ -1418,9 +1418,10 @@ class AstFork final : public AstNodeBlock {
// spawned. This is necessary to implement things like local variable
// initializers properly. The parallel statements inside the fork must all
// be AstBegin, as lowering stages will introduce additional statements to
// be executed sequentially within eaach fork branch.
// be executed sequentially within each fork branch.
//
// @astgen op3 := forksp : List[AstBegin]
// @astgen op4 := parentProcessp : Optional[AstVarRef]
const VJoinType m_joinType; // Join keyword type
public:
AstFork(FileLine* fl, VJoinType joinType, const string& name = "")

View File

@ -1251,6 +1251,8 @@ public:
puts(");\n");
}
void visit(AstFinish* nodep) override {
// Disable all the forks so they don't operate after simulation is finished.
if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n");
putns(nodep, "VL_FINISH_MT(");
putsQuoted(protect(nodep->fileline()->filename()));
puts(", ");
@ -1258,6 +1260,8 @@ public:
puts(", \"\");\n");
}
void visit(AstFinishFork* nodep) override {
// Disable all the forks so they don't operate after simulation is finished.
if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n");
putns(nodep, "VL_FINISH_MT(");
putsQuoted(protect(nodep->fileline()->filename()));
puts(", ");

View File

@ -524,6 +524,11 @@ class ForkVisitor final : public VNVisitor {
AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables
AstArg* m_capturedArgsp = nullptr; // References to captured variables (as args)
// STATE - across all visitors
AstClass* m_processClassp = nullptr;
AstFunc* m_statusMethodp = nullptr;
VMemberMap m_memberMap; // for lookup of process class methods
// METHODS
AstVar* capture(AstVarRef* refp) {
AstVar* varp = nullptr;
@ -577,6 +582,19 @@ class ForkVisitor final : public VNVisitor {
return true;
}
AstClass* getProcessClassp() {
if (!m_processClassp)
m_processClassp
= VN_AS(m_memberMap.findMember(v3Global.rootp()->stdPackagep(), "process"), Class);
return m_processClassp;
}
AstFunc* getStatusmethodp() {
if (m_statusMethodp == nullptr)
m_statusMethodp = VN_AS(m_memberMap.findMember(getProcessClassp(), "status"), Func);
return m_statusMethodp;
}
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -596,6 +614,34 @@ class ForkVisitor final : public VNVisitor {
return;
}
// IEEE 1800-2023 9.3.2: In all cases, processes spawned by a fork-join block shall not
// start executing until the parent process is blocked or terminates.
// Because join and join_any block the parent process, it is only needed when join_none
// is used.
if (nodep->joinType().joinNone()) {
UINFO(9, "Visiting fork..join_none " << nodep);
FileLine* fl = nodep->fileline();
AstVarRef* forkParentrefp = nodep->parentProcessp();
if (forkParentrefp) { // Forks created by V3Fork will not have this
for (AstBegin *itemp = nodep->forksp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), Begin);
if (!itemp->stmtsp()) continue;
AstMethodCall* const statusCallp = new AstMethodCall{
fl, forkParentrefp->cloneTree(false), "status", nullptr};
statusCallp->taskp(getStatusmethodp());
statusCallp->classOrPackagep(getProcessClassp());
statusCallp->dtypep(getStatusmethodp()->dtypep());
AstNeq* const condp
= new AstNeq{fl, statusCallp,
new AstConst{fl, AstConst::WidthedValue{},
getStatusmethodp()->dtypep()->width(), 1}};
AstWait* const waitStmt = new AstWait{fl, condp, nullptr};
itemp->stmtsp()->addHereThisAsNext(waitStmt);
}
}
}
iterateAndNextNull(nodep->declsp());
iterateAndNextNull(nodep->stmtsp());
std::vector<AstBegin*> wrappedp;

View File

@ -622,6 +622,29 @@ private:
return findp;
}
VSymEnt* findForkParentAlias(VSymEnt* symp, const string& ident) {
static const string suffix = "__VgetForkParent";
VSymEnt* const wrapperp = symp->findIdFlat(ident + suffix);
if (!wrapperp) return nullptr;
if (!VN_IS(wrapperp->nodep(), Begin)) return nullptr;
if (VSymEnt* const forkSymp = wrapperp->findIdFlat(ident)) {
if (VN_IS(forkSymp->nodep(), Fork)) return forkSymp;
}
return nullptr;
}
VSymEnt* unwrapForkParent(VSymEnt* symp, const string& ident) {
static const string suffix = "__VgetForkParent";
if (AstBegin* const beginp = VN_CAST(symp->nodep(), Begin)) {
if (VString::endsWith(beginp->name(), suffix)) {
if (VSymEnt* const forkSymp = symp->findIdFlat(ident)) {
if (VN_IS(forkSymp->nodep(), Fork)) return forkSymp;
}
}
}
return symp;
}
VSymEnt* findWithAltFlat(VSymEnt* symp, const string& name, const string& altname) {
VSymEnt* findp = symp->findIdFlat(name);
if (findp) return findp;
@ -672,9 +695,10 @@ public:
const AstCellInline* inlinep = lookupSymp
? VN_CAST(lookupSymp->nodep(), CellInline)
: nullptr; // Replicated below
if (VSymEnt* const findSymp = findWithAltFallback(lookupSymp, ident, altIdent)) {
lookupSymp = findSymp;
}
VSymEnt* findSymp = findWithAltFallback(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp) lookupSymp = unwrapForkParent(findSymp, ident);
// Check this module - cur modname
else if ((cellp && cellp->modp()->origName() == ident)
|| (inlinep && inlinep->origModName() == ident)) {
@ -720,9 +744,11 @@ public:
if (!lookupSymp) return nullptr; // Not found
}
} else { // Searching for middle submodule, must be a cell name
if (VSymEnt* const findSymp = findWithAltFlat(lookupSymp, ident, altIdent)) {
lookupSymp = findSymp;
} else {
VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp)
lookupSymp = unwrapForkParent(findSymp, ident);
else {
return nullptr; // Not found
}
}

View File

@ -23,6 +23,7 @@
#include "V3LinkParse.h"
#include "V3Control.h"
#include "V3MemberMap.h"
#include "V3Stats.h"
#include <set>
@ -46,6 +47,7 @@ class LinkParseVisitor final : public VNVisitor {
// STATE - across all visitors
std::unordered_set<FileLine*> m_filelines; // Filelines that have been seen
VMemberMap m_memberMap; // for lookup of process class methods
// STATE - for current visit position (use VL_RESTORER)
// If set, move AstVar->valuep() initial values to this module
@ -72,6 +74,9 @@ class LinkParseVisitor final : public VNVisitor {
// STATE - Statistic tracking
VDouble0 m_statModules; // Number of modules seen
bool m_unprotectedStdProcess
= false; // Set when std::process internals were unprotected, we only need to do this once
// METHODS
void cleanFileline(AstNode* nodep) {
if (nodep->user2SetOnce()) return; // Process once
@ -105,6 +110,21 @@ class LinkParseVisitor final : public VNVisitor {
return "";
}
void unprotectStdProcessHandle() {
if (m_unprotectedStdProcess) return;
m_unprotectedStdProcess = true;
if (!v3Global.opt.protectIds()) return;
if (AstPackage* const stdp = v3Global.rootp()->stdPackagep()) {
if (AstClass* const processp
= VN_CAST(m_memberMap.findMember(stdp, "process"), Class)) {
if (AstVar* const handlep
= VN_CAST(m_memberMap.findMember(processp, "m_process"), Var)) {
handlep->protect(false);
}
}
}
}
void visitIterateNodeDType(AstNodeDType* nodep) {
if (nodep->user1SetOnce()) return; // Process only once.
cleanFileline(nodep);
@ -180,6 +200,38 @@ class LinkParseVisitor final : public VNVisitor {
<< nodep->warnContextSecondary());
}
void addForkParentProcess(AstFork* forkp) {
FileLine* const fl = forkp->fileline();
const std::string parentName = "__VforkParent";
AstRefDType* const dtypep = new AstRefDType{fl, "process"};
AstVar* const parentVar
= new AstVar{fl, VVarType::BLOCKTEMP, parentName, VFlagChildDType{}, dtypep};
parentVar->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
AstParseRef* const lhsp = new AstParseRef{fl, parentName, nullptr, nullptr};
AstClassOrPackageRef* const processRefp
= new AstClassOrPackageRef{fl, "process", nullptr, nullptr};
AstParseRef* const selfRefp = new AstParseRef{fl, "self", nullptr, nullptr};
AstDot* const processSelfp = new AstDot{fl, true, processRefp, selfRefp};
AstMethodCall* const callp = new AstMethodCall{fl, processSelfp, "self", nullptr};
AstAssign* const initp = new AstAssign{fl, lhsp, callp};
AstVarRef* const parentRefp = new AstVarRef{fl, parentVar, VAccess::READ};
forkp->parentProcessp(parentRefp);
VNRelinker relinker;
forkp->unlinkFrBack(&relinker);
parentVar->addNextHere(initp);
initp->addNextHere(forkp);
AstBegin* const beginp = new AstBegin{
fl, forkp->name() == "" ? "" : forkp->name() + "__VgetForkParent", parentVar, true};
relinker.relink(beginp);
}
// VISITORS
void visit(AstNodeFTask* nodep) override {
if (nodep->user1SetOnce()) return; // Process only once.
@ -791,7 +843,11 @@ class LinkParseVisitor final : public VNVisitor {
}
cleanFileline(nodep);
iterateAndNextNull(nodep->stmtsp());
if (AstFork* const forkp = VN_CAST(nodep, Fork)) iterateAndNextNull(forkp->forksp());
if (AstFork* const forkp = VN_CAST(nodep, Fork)) {
iterateAndNextNull(forkp->forksp());
if (!forkp->parentProcessp() && forkp->joinType().joinNone() && forkp->forksp())
addForkParentProcess(forkp);
}
}
void visit(AstCase* nodep) override {
V3Control::applyCase(nodep);
@ -954,7 +1010,10 @@ class LinkParseVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit LinkParseVisitor(AstNetlist* rootp) { iterate(rootp); }
explicit LinkParseVisitor(AstNetlist* rootp) {
unprotectStdProcessHandle();
iterate(rootp);
}
~LinkParseVisitor() override {
V3Stats::addStatSum(V3Stats::STAT_SOURCE_MODULES, m_statModules);
}

View File

@ -89,6 +89,10 @@ private:
for (AstNode* itemp = anodep->membersp(); itemp; itemp = itemp->nextp()) {
memberInsert(mmapr, itemp);
}
} else if (const AstPackage* const anodep = VN_CAST(nodep, Package)) {
for (AstNode* itemp = anodep->stmtsp(); itemp; itemp = itemp->nextp()) {
memberInsert(mmapr, itemp);
}
} else {
nodep->v3fatalSrc("Unsupported node type");
}

View File

@ -340,4 +340,12 @@ public:
}
return resp;
}
// Wrap fork statements in AstBegin, ensure fork...join_none have process
static AstNodeStmt* wrapFork(V3ParseImp* parsep, AstFork* forkp, AstNodeStmt* stmtsp) {
if (forkp->joinType() == VJoinType::JOIN_NONE && stmtsp)
parsep->importIfInStd(forkp->fileline(), "process", true);
forkp->addForksp(wrapInBegin(stmtsp));
return forkp;
}
};

View File

@ -469,6 +469,7 @@ class TimingControlVisitor final : public VNVisitor {
AstScope* m_scopep = nullptr; // Current scope
AstActive* m_activep = nullptr; // Current active
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
bool m_hasProcess = false; // True if current scope has a VlProcess handle available
int m_forkCnt = 0; // Number of forks inside a module
bool m_underJumpBlock = false; // True if we are inside of a jump-block
bool m_underProcedure = false; // True if we are under an always or initial
@ -654,26 +655,34 @@ class TimingControlVisitor final : public VNVisitor {
}
// Adds debug info to a hardcoded method call
void addDebugInfo(AstCMethodHard* const methodp) const {
if (v3Global.opt.protectIds()) return;
FileLine* const flp = methodp->fileline();
AstCExpr* const ap = new AstCExpr{flp, '"' + flp->filenameEsc() + '"'};
const bool protectIds = v3Global.opt.protectIds();
AstCExpr* const ap
= new AstCExpr{flp, protectIds ? "VL_UNKNOWN" : '"' + flp->filenameEsc() + '"'};
ap->dtypeSetString();
methodp->addPinsp(ap);
AstCExpr* const bp = new AstCExpr{flp, cvtToStr(flp->lineno())};
AstCExpr* const bp = new AstCExpr{flp, protectIds ? "0" : cvtToStr(flp->lineno())};
bp->dtypeSetString();
methodp->addPinsp(bp);
}
// Adds debug info to a trigSched.trigger() call
void addEventDebugInfo(AstCMethodHard* const methodp, AstSenTree* const sentreep) const {
if (v3Global.opt.protectIds()) return;
if (v3Global.opt.protectIds()) {
FileLine* const flp = sentreep->fileline();
AstCExpr* const descp = new AstCExpr{flp, "VL_UNKNOWN"};
descp->dtypeSetString();
methodp->addPinsp(descp);
addDebugInfo(methodp);
return;
}
methodp->addPinsp(createEventDescription(sentreep));
addDebugInfo(methodp);
}
// Adds process pointer to a hardcoded method call
void addProcessInfo(AstCMethodHard* const methodp) const {
FileLine* const flp = methodp->fileline();
AstCExpr* const ap = new AstCExpr{
flp, m_procp && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr"};
AstCExpr* const ap
= new AstCExpr{flp, (m_procp && m_hasProcess) ? "vlProcess" : "nullptr"};
methodp->addPinsp(ap);
}
// Creates the fork handle type and returns it
@ -779,7 +788,9 @@ class TimingControlVisitor final : public VNVisitor {
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
VL_RESTORER(m_hasProcess);
m_procp = nodep;
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
VL_RESTORER(m_underProcedure);
m_underProcedure = true;
iterateChildren(nodep);
@ -801,7 +812,9 @@ class TimingControlVisitor final : public VNVisitor {
void visit(AstAlways* nodep) override {
if (nodep->user1SetOnce()) return;
VL_RESTORER(m_procp);
VL_RESTORER(m_hasProcess);
m_procp = nodep;
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
VL_RESTORER(m_underProcedure);
m_underProcedure = true;
// Workaround for killing `always` processes (doing that is pretty much UB)
@ -829,7 +842,9 @@ class TimingControlVisitor final : public VNVisitor {
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_procp);
VL_RESTORER(m_hasProcess);
m_procp = nodep;
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
iterateChildren(nodep);
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (!(hasFlags(nodep, T_SUSPENDEE))) return;
@ -862,6 +877,7 @@ class TimingControlVisitor final : public VNVisitor {
}
}
void visit(AstNodeCCall* nodep) override {
if (nodep->funcp()->needProcess()) m_hasProcess = true;
if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
@ -1165,12 +1181,12 @@ class TimingControlVisitor final : public VNVisitor {
forkp->unlinkFrBack()});
}
void visit(AstDisableFork* nodep) override {
if (hasFlags(m_procp, T_HAS_PROC)) return;
if (m_hasProcess) return;
// never reached by any process; remove to avoid compilation error
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstWaitFork* nodep) override {
if (hasFlags(m_procp, T_HAS_PROC)) {
if (m_hasProcess) {
FileLine* const flp = nodep->fileline();
AstCExpr* const exprp = new AstCExpr{flp, "vlProcess->completedFork()", 1};
AstWait* const waitp = new AstWait{flp, exprp, nullptr};
@ -1233,8 +1249,10 @@ class TimingControlVisitor final : public VNVisitor {
}
void visit(AstBegin* nodep) override {
VL_RESTORER(m_procp);
VL_RESTORER(m_hasProcess);
m_hasProcess |= hasFlags(nodep, T_HAS_PROC);
m_procp = nodep;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (m_hasProcess) nodep->setNeedProcess();
iterateChildren(nodep);
}
void visit(AstFork* nodep) override {

View File

@ -3400,23 +3400,23 @@ par_blockJoin<joinType>:
| yJOIN_NONE { $$ = VJoinType::JOIN_NONE; }
;
par_block<forkp>: // ==IEEE: par_block
par_block<nodeStmtp>: // ==IEEE: par_block
yFORK startLabelE blockDeclListE stmtListE par_blockJoin endLabelE
{
$$ = new AstFork{$1, $5, $2 ? *$2 : ""};
GRAMMARP->endLabel($<fl>6, $$, $6);
$$->addDeclsp($3);
$$->addForksp(V3ParseGrammar::wrapInBegin($4));
AstFork* const forkp = new AstFork{$1, $5, $2 ? *$2 : ""};
GRAMMARP->endLabel($<fl>6, forkp, $6);
forkp->addDeclsp($3);
$$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $4);
}
;
par_blockPreId<forkp>: // ==IEEE: par_block but called with leading ID
par_blockPreId<nodeStmtp>: // ==IEEE: par_block but called with leading ID
id yP_COLON__FORK yFORK blockDeclListE stmtListE par_blockJoin endLabelE
{
$$ = new AstFork{$3, $6, *$1};
GRAMMARP->endLabel($<fl>7, $$, $7);
$$->addDeclsp($4);
$$->addForksp(V3ParseGrammar::wrapInBegin($5));
AstFork* const forkp = new AstFork{$3, $6, *$1};
GRAMMARP->endLabel($<fl>7, forkp, $7);
forkp->addDeclsp($4);
$$ = V3ParseGrammar::wrapFork(PARSEP, forkp, $5);
}
;

View File

@ -28,6 +28,7 @@ class Cls;
y = 2;
end
join_none
#1;
endtask
endclass

View File

@ -17,6 +17,7 @@ class Foo;
$stop;
end
join_none : frk
#1;
endtask
endclass

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--timing"])
test.execute()
test.passes()

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, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t;
export "DPI-C" task cfunc_finish; // this is just so the task becomes AstCFunc, we don't really use the export
task cfunc_finish;
$finish;
endtask
initial begin
fork
cfunc_finish();
join_none
#1 $stop;
end
endmodule

18
test_regress/t/t_fork_delay.py Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,16 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t;
integer i=0;
initial begin
fork
i=1;
join_none
if(i==1) $stop;
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,15 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t;
bit flag;
initial begin
fork begin
$stop;
end join_none
$finish;
end
endmodule

View File

@ -20,9 +20,10 @@ module t;
fork
p = 1;
join_none
#0;
endtask
task t2(output q);
q <= 1;
q = 1;
endtask
endmodule

View File

@ -8,12 +8,15 @@ module t;
process job[] = new [8];
initial begin
foreach (job[j]) fork
begin
$write("job started\n");
job[j] = process::self();
end
join_none
foreach (job[j]) begin
fork
begin
$write("job started\n");
job[j] = process::self();
end
join_none
#0;
end
foreach (job[j]) begin
wait (job[j]);
end

View File

@ -0,0 +1,4 @@
job started
all jobs started
all jobs finished
*-* All Finished *-*

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute(expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,26 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
process job;
initial begin
process p1 = process::self();
fork
begin
wait(p1.status() != process::RUNNING);
$write("job started\n");
job = process::self();
end
join_none
wait (job);
$write("all jobs started\n");
job.await();
$write("all jobs finished\n");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -50,7 +50,7 @@ module t();
bar.ewait();
end
join_none
#1;
p.kill();
->evt1;

View File

@ -2,7 +2,10 @@
-V{t#,#}+ Vt_timing_debug2___024root___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_std___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_std__03a__03asemaphore__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10__Vclpkg___ctor_var_reset
@ -1207,7 +1210,12 @@
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_sth_else
-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_delay
-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_sth_else
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg::__VnoInFunc_self
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::new
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__Vtiming__6____Vfork_1__0
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status
-V{t#,#} Suspending process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_0__25____VforkParent.(t.__Vtask_status__26__Vfuncout); , ); )) at t/t_timing_class.v:224
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:76
-V{t#,#} Process forked at t/t_timing_class.v:76 finished
@ -1217,8 +1225,13 @@
-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act
-V{t#,#} Suspended processes waiting for dynamic trigger evaluation:
-V{t#,#} - Process waiting at t/t_timing_class.v:75
-V{t#,#} Suspended processes waiting for dynamic trigger evaluation:
-V{t#,#} - Process waiting at t/t_timing_class.v:224
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:75
-V{t#,#} Process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:75 awaiting resumption
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status
-V{t#,#} Process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_0__25____VforkParent.(t.__Vtask_status__26__Vfuncout); , ); )) at t/t_timing_class.v:224 awaiting resumption
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
@ -1228,9 +1241,11 @@
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
-V{t#,#} Resuming processes:
-V{t#,#} - Process waiting at t/t_timing_class.v:75
-V{t#,#} - Process waiting at t/t_timing_class.v:224
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:75
-V{t#,#} Process forked at t/t_timing_class.v:75 finished
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:74
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224
-V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act
-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act
-V{t#,#} No suspended processes waiting for dynamic trigger evaluation
@ -1271,15 +1286,15 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
-V{t#,#} Delayed processes:
-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:224
-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:131
-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:224
-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:136
-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:190
-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:274
-V{t#,#} Resuming delayed processes
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:131
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::__VnoInFunc_flip
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:224
-V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act
-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act
-V{t#,#} No suspended processes waiting for dynamic trigger evaluation
@ -1327,12 +1342,34 @@
-V{t#,#} Resuming delayed processes
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:136
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:190
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess__Vclpkg::__VnoInFunc_self
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::new
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__Vtiming__6____Vfork_2__0
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status
-V{t#,#} Suspending process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_1__29____VforkParent.(t.__Vtask_status__30__Vfuncout); , ); )) at t/t_timing_class.v:229
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:131
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::__VnoInFunc_flip
-V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act
-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act
-V{t#,#} Suspended processes waiting for dynamic trigger evaluation:
-V{t#,#} - Process waiting at t/t_timing_class.v:229
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:229
-V{t#,#}+ Vt_timing_debug2_std__03a__03aprocess::__VnoInFunc_status
-V{t#,#} Process waiting for @([true] (32'h1 != $_EXPRSTMT( // Function: status t.__Vtask___VforkTask_1__29____VforkParent.(t.__Vtask_status__30__Vfuncout); , ); )) at t/t_timing_class.v:229 awaiting resumption
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___timing_commit
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
-V{t#,#} Resuming processes:
-V{t#,#} - Process waiting at t/t_timing_class.v:229
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:229
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign
-V{t#,#}+ Vt_timing_debug2___024root___eval_phase__act
-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act
-V{t#,#} No suspended processes waiting for dynamic trigger evaluation
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary -Wno-UNOPTFLAT"])
test.compile(verilator_flags2=["--binary"])
test.execute()

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_timing_fork_comb.v"
# Should convert the first always into combo and detect cycle
test.lint(fails=True, verilator_flags2=["--timing"])
test.file_grep(
test.compile_log_filename,
r'%Warning-UNOPTFLAT: t/t_timing_fork_comb.v:\d+:\d+: Signal unoptimizable: Circular combinational logic:'
)
test.passes()