Fix #0 delays to control fork scheduling (#6730 repair) (#6891)

Signed-off-by: Artur Bieniek <abieniek@internships.antmicro.com>
This commit is contained in:
Artur Bieniek 2026-02-18 03:51:11 +01:00 committed by GitHub
parent 7176bdcff6
commit da28c67e60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 288 additions and 209 deletions

View File

@ -160,6 +160,7 @@ void vl_finish(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE
#ifndef VL_USER_STOP ///< Define this to override the vl_stop function
void vl_stop(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE {
// $stop or $fatal reporting; would break current API to add param as to which
if (Verilated::threadContextp()->gotFinish()) return;
const char* const msg = "Verilog $stop";
Verilated::threadContextp()->gotError(true);
Verilated::threadContextp()->gotFinish(true);

View File

@ -94,7 +94,7 @@ uint64_t VlDelayScheduler::nextTimeSlot() const {
#ifdef VL_DEBUG
void VlDelayScheduler::dump() const {
if (m_queue.empty()) {
if (m_queue.empty() && m_zeroDelayed.empty()) {
VL_DBG_MSGF(" No delayed processes:\n");
} else {
VL_DBG_MSGF(" Delayed processes:\n");

View File

@ -28,6 +28,7 @@
#include "verilated.h"
#include <limits>
#include <vector>
// clang-format off
@ -209,7 +210,8 @@ public:
bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
if (phase == VlDelayPhase::ACTIVE) {
// Both active delays and fork..join_none #0 are resumed out of the time queue.
if (phase != VlDelayPhase::INACTIVE) {
queue.emplace(delay, VlCoroutineHandle{coro, process, fileline});
} else {
queueZeroDelay.emplace_back(VlCoroutineHandle{coro, process, fileline});
@ -218,7 +220,14 @@ public:
void await_resume() const {}
};
const VlDelayPhase phase = (delay == 0) ? VlDelayPhase::INACTIVE : VlDelayPhase::ACTIVE;
VlDelayPhase phase;
if (delay != 0) {
// UINT64_MAX is a sentinel for synthetic fork..join_none delays.
if (delay == std::numeric_limits<uint64_t>::max()) delay = 0;
phase = VlDelayPhase::ACTIVE;
} else {
phase = VlDelayPhase::INACTIVE;
}
return Awaitable{process, m_queue,
m_zeroDelayed, m_context.time() + delay,
phase, VlFileLineDebug{filename, lineno}};

View File

@ -1421,7 +1421,6 @@ class AstFork final : public AstNodeBlock {
// 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

@ -47,6 +47,7 @@
#include "V3AstNodeExpr.h"
#include "V3MemberMap.h"
#include <limits>
#include <set>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -90,7 +91,7 @@ public:
m_instance.m_handlep
= new AstVar{m_procp->fileline(), VVarType::BLOCKTEMP,
generateDynScopeHandleName(m_procp), m_instance.m_refDTypep};
m_instance.m_handlep->funcLocal(true);
m_instance.m_handlep->funcLocal(false);
m_instance.m_handlep->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
UINFO(9, "new dynscope var " << m_instance.m_handlep);
@ -336,12 +337,14 @@ class DynScopeVisitor final : public VNVisitor {
}
bool needsDynScope(const AstVarRef* refp) const {
const AstVar* const varp = refp->varp();
const bool localLifetime = varp->isFuncLocal() || varp->lifetime().isAutomatic();
return
// Can this variable escape the scope
((m_forkDepth > refp->varp()->user1()) && refp->varp()->isFuncLocal())
((m_forkDepth > varp->user1()) && localLifetime)
&& (
// Is it mutated
(refp->varp()->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW())
(varp->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW())
// Or is it after a timing-control event
|| m_afterTimingControl);
}
@ -527,11 +530,6 @@ 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;
@ -585,19 +583,6 @@ 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);
@ -624,24 +609,16 @@ class ForkVisitor final : public VNVisitor {
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);
}
// We use a sentinel value of UINT64_MAX to mark this delay so that it goes to the
// ACTIVE region with a delay value of 0.
for (AstBegin *itemp = nodep->forksp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), Begin);
if (!itemp->stmtsp()) continue;
AstDelay* const delayp = new AstDelay{
fl,
new AstConst{fl, AstConst::Unsized64{}, std::numeric_limits<uint64_t>::max()},
false};
itemp->stmtsp()->addHereThisAsNext(delayp);
}
}

View File

@ -653,29 +653,6 @@ 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;
@ -726,9 +703,9 @@ public:
const AstCellInline* inlinep = lookupSymp
? VN_CAST(lookupSymp->nodep(), CellInline)
: nullptr; // Replicated below
VSymEnt* findSymp = findWithAltFallback(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp) lookupSymp = unwrapForkParent(findSymp, ident);
if (VSymEnt* const findSymp = findWithAltFallback(lookupSymp, ident, altIdent)) {
lookupSymp = findSymp;
}
// Check this module - cur modname
else if ((cellp && cellp->modp()->origName() == ident)
@ -776,9 +753,8 @@ public:
}
} else { // Searching for middle path component, must be a cell or interface port
VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp) {
lookupSymp = unwrapForkParent(findSymp, ident);
lookupSymp = findSymp;
} else {
// Try prefixed lookup for interface ports accessed through hierarchy
// (e.g., slave_inst.bus.data where bus is an interface port)

View File

@ -23,7 +23,6 @@
#include "V3LinkParse.h"
#include "V3Control.h"
#include "V3MemberMap.h"
#include "V3Stats.h"
#include <set>
@ -47,7 +46,6 @@ 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
@ -75,9 +73,6 @@ 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
@ -111,21 +106,6 @@ 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);
@ -201,38 +181,6 @@ 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.
@ -319,6 +267,10 @@ class LinkParseVisitor final : public VNVisitor {
cleanFileline(nodep);
UINFO(9, "VAR " << nodep);
if (nodep->valuep()) nodep->hasUserInit(true);
if (m_insideLoop && nodep->lifetime().isNone() && nodep->varType() == VVarType::VAR
&& !nodep->direction().isAny()) {
nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT);
}
if (nodep->lifetime().isStatic() && m_insideLoop && nodep->valuep()) {
nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT);
nodep->v3warn(STATICVAR, "Static variable with assignment declaration declared in a "
@ -854,11 +806,7 @@ class LinkParseVisitor final : public VNVisitor {
}
cleanFileline(nodep);
iterateAndNextNull(nodep->stmtsp());
if (AstFork* const forkp = VN_CAST(nodep, Fork)) {
iterateAndNextNull(forkp->forksp());
if (!forkp->parentProcessp() && forkp->joinType().joinNone() && forkp->forksp())
addForkParentProcess(forkp);
}
if (AstFork* const forkp = VN_CAST(nodep, Fork)) iterateAndNextNull(forkp->forksp());
}
void visit(AstCase* nodep) override {
V3Control::applyCase(nodep);
@ -1066,10 +1014,7 @@ class LinkParseVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit LinkParseVisitor(AstNetlist* rootp) {
unprotectStdProcessHandle();
iterate(rootp);
}
explicit LinkParseVisitor(AstNetlist* rootp) { iterate(rootp); }
~LinkParseVisitor() override {
V3Stats::addStatSum(V3Stats::STAT_SOURCE_MODULES, m_statModules);
}

View File

@ -364,12 +364,4 @@ 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

@ -210,9 +210,11 @@ private:
// Find all var->varscope mappings, for later cleanup
for (AstNode* stmtp = nodep->varsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVarScope* const vscp = VN_CAST(stmtp, VarScope)) {
if (vscp->varp()->isFuncLocal() || vscp->varp()->isUsedLoopIdx()) {
AstVar* const varp = vscp->varp();
if (varp->isFuncLocal() || varp->isUsedLoopIdx()
|| varp->lifetime().isAutomatic()) {
UINFO(9, " funcvsc " << vscp);
m_varToScopeMap.emplace(std::make_pair(nodep, vscp->varp()), vscp);
m_varToScopeMap.emplace(std::make_pair(nodep, varp), vscp);
}
}
}

View File

@ -74,6 +74,7 @@
#include "V3Stats.h"
#include "V3UniqueNames.h"
#include <limits>
#include <queue>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -627,9 +628,10 @@ class TimingControlVisitor final : public VNVisitor {
// Returns true if the given trigger expression needs a destructive post update after trigger
// evaluation. Currently this only applies to named events.
bool destructivePostUpdate(AstNode* const exprp) const {
return exprp->exists([](const AstNodeVarRef* const refp) {
AstBasicDType* const dtypep = refp->dtypep()->basicp();
return dtypep && dtypep->isEvent();
return exprp->exists([](const AstNode* const nodep) {
const AstNodeDType* const dtypep = nodep->dtypep();
const AstBasicDType* const basicp = dtypep ? dtypep->skipRefp()->basicp() : nullptr;
return basicp && basicp->isEvent();
});
}
// Creates a trigger scheduler variable
@ -933,21 +935,28 @@ class TimingControlVisitor final : public VNVisitor {
valuep = new AstConst{flp, AstConst::Unsized64{}, 1};
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
} else {
// Scale the delay
const double timescaleFactorD = calculateTimescaleFactor(nodep, nodep->timeunit());
if (valuep->dtypep()->skipRefp()->isDouble()) {
AstConst* const tsfp = new AstConst{flp, AstConst::RealDouble{}, timescaleFactorD};
valuep = new AstMulD{flp, valuep, tsfp};
valuep = new AstRToIRoundS{flp, valuep};
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
} else {
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
const uint64_t timescaleFactorU = static_cast<uint64_t>(timescaleFactorD);
AstConst* const tsfp = new AstConst{flp, AstConst::Unsized64{}, timescaleFactorU};
valuep = new AstMul{flp, valuep, tsfp};
AstConst* const constp = VN_CAST(valuep, Const);
const bool isForkSentinel
= constp && (constp->toUQuad() == std::numeric_limits<uint64_t>::max());
if (!isForkSentinel && (!constp || !constp->isZero())) {
// Scale the delay
const double timescaleFactorD = calculateTimescaleFactor(nodep, nodep->timeunit());
if (valuep->dtypep()->skipRefp()->isDouble()) {
AstConst* const tsfp
= new AstConst{flp, AstConst::RealDouble{}, timescaleFactorD};
valuep = new AstMulD{flp, valuep, tsfp};
valuep = new AstRToIRoundS{flp, valuep};
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
} else {
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
const uint64_t timescaleFactorU = static_cast<uint64_t>(timescaleFactorD);
AstConst* const tsfp
= new AstConst{flp, AstConst::Unsized64{}, timescaleFactorU};
valuep = new AstMul{flp, valuep, tsfp};
}
// Simplify
valuep = V3Const::constifyEdit(valuep);
}
// Simplify
valuep = V3Const::constifyEdit(valuep);
}
// Statistics

View File

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

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2026 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()
test.passes()

View File

@ -0,0 +1,29 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module test;
mailbox #(int) mbox;
initial begin
mbox = new();
mbox.put(10);
mbox.put(30);
repeat(2) begin
int item;
mbox.get(item);
fork
begin
$display("got", item);
if(item==10)
$finish;
end
join_none
end
#0;
$stop;
end
endmodule

View File

@ -0,0 +1,3 @@
This should be first
This should be second
This should be last

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2026 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,15 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
initial fork
#0 $write("This should be last\n");
begin
fork $write("This should be second\n"); join_none
$write("This should be first\n");
end
join_none
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2026 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()
test.passes()

View File

@ -0,0 +1,30 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module test;
mailbox #(int) mbox;
initial begin
mbox = new();
fork
repeat(2) begin
int val;
mbox.get(val);
fork
fork
begin
$finish;
end
join_none
join_none
end
join_none
mbox.put(1);
#1;
$stop;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2026 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()
test.passes()

View File

@ -0,0 +1,35 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class events_holder;
event ev;
endclass
module test;
events_holder m_events[int];
int waiters_done = 0;
initial begin
m_events[0] = new;
fork
begin
@(m_events[0].ev);
waiters_done++;
end
begin
@(m_events[0].ev);
waiters_done++;
end
join_none
#1;
->m_events[0].ev;
#1;
if (waiters_done == 2) $finish;
end
initial #10000ns $stop;
endmodule

View File

@ -2,10 +2,7 @@
-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
@ -1342,28 +1339,19 @@
-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); , 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
-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_vec__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); , t.__Vtask_status__26__Vfuncout); )) at t/t_timing_class.v:224 awaiting resumption
-V{t#,#}+ Vt_timing_debug2___024root___timing_ready
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
@ -1376,11 +1364,9 @@
-V{t#,#} Resuming processes waiting for @([event] t.ec.e)
-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_vec__act
-V{t#,#} No suspended processes waiting for dynamic trigger evaluation
@ -1491,33 +1477,9 @@
-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_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); , 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_vec__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); , t.__Vtask_status__30__Vfuncout); )) at t/t_timing_class.v:229 awaiting resumption
-V{t#,#}+ Vt_timing_debug2___024root___timing_ready
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-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___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
-V{t#,#} No process to resume waiting for @([event] t.ec.e)
-V{t#,#} Resuming processes waiting for @([event] t.ec.e)
-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

View File

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

View File

@ -0,0 +1,23 @@
#!/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()