From da13029b0a834e8021d155d1d069b310c83ef678 Mon Sep 17 00:00:00 2001 From: Igor Zaworski Date: Mon, 6 Jul 2026 17:43:53 +0200 Subject: [PATCH 1/2] [#99211] Fix scheduler lost trigger - optimized Signed-off-by: Igor Zaworski --- src/V3SchedTiming.cpp | 94 ++++++++++++++++++- .../t/t_sched_noninlined_func_suspendable.py | 21 +++++ .../t/t_sched_noninlined_func_suspendable.v | 93 ++++++++++++++++++ test_regress/t/t_timing_debug2.out | 56 +++++------ 4 files changed, 232 insertions(+), 32 deletions(-) create mode 100755 test_regress/t/t_sched_noninlined_func_suspendable.py create mode 100644 test_regress/t/t_sched_noninlined_func_suspendable.v diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 5e9e427cd..afce44efd 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -26,6 +26,7 @@ #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT +#include "V3AstUserAllocator.h" #include "V3EmitCBase.h" #include "V3Sched.h" @@ -179,7 +180,8 @@ AstCCall* TimingKit::createReady(AstNetlist* const netlistp) { class AwaitVisitor final : public VNVisitor { // NODE STATE - // AstSenTree::user1() -> bool. Set true if the sentree has been visited. + // AstSenTree::user1() -> bool. Set true if the sentree has been visited. + // AstCFunc::user1() -> AstUser1Allocator. See alocator below const VNUser1InUse m_inuser1; // STATE @@ -192,7 +194,34 @@ class AwaitVisitor final : public VNVisitor { std::map>& m_externalDomains; std::set m_processDomains; // Sentrees from the current process // Variables written by suspendable processes - std::vector m_writtenBySuspendable; + std::set m_writtenBySuspendable; + struct CFuncCache final { + std::set m_processDomains; // What shall be added to m_processDomains + std::set + m_writtenBySuspendable; // What shall be added to m_writtenBySuspendable + std::set m_includes; // CFuncs whose CFuncCache shall be included into this + // m_includes does not need to keep bool with m_gatherVars to know which cache shall be + // used since: + // Only possible transition in a graph is from `!m_gatherVars` to `m_gatherVars` + // (in visit(AstFork*) there is a only possible transition, visit(AstNodeProcedure*) is + // not considered since it does not occur during a graph traversal) + // therefore, if change of state occurs it is certain that the first node after transition + // won't be in visiting state + // therefore, every node in a m_includes shall take value of m_gatherVars under which + // current CFuncCache is + enum State : uint8_t { + UNINITIALIZED = 0, // Not initialized members are empty + VISITING, // Visiting - needed for breaking recursion + INITIALIZED, // Members contains correct values + } m_state // Current state of Cache + = UNINITIALIZED; + }; + std::vector m_callStack; // Current callstack of AstCFuncs + // Caches how visiting the function with given value of m_gatherVars changes + // m_processDomains and m_writtenBySuspendable - only accessed from visit(AstCFunc* nodep) + AstUser1Allocator> m_cfuncsCache; + // Count uses of not inlined writes to signals in suspendables + VDouble0 m_notInlinedWritesInSuspendableUsage; // METHODS // Add arguments to a resume() call based on arguments in the suspending call @@ -267,13 +296,67 @@ class AwaitVisitor final : public VNVisitor { if (!sentreep->user1SetOnce()) createResumeActive(nodep); if (m_inProcess) m_processDomains.insert(sentreep); } + iterateChildren(nodep); } void visit(AstNodeVarRef* nodep) override { if (m_gatherVars && nodep->access().isWriteOrRW() && !nodep->varp()->ignoreSchedWrite() && !nodep->varScopep()->user2SetOnce()) { - m_writtenBySuspendable.push_back(nodep->varScopep()); + m_writtenBySuspendable.insert(nodep->varScopep()); } } + void visit(AstNodeCCall* const nodep) override { + iterateChildren(nodep); + // We need to visit bodies of non-inlined functions + visitCalledCFunc(nodep->funcp()); + } + void visit(AstCFunc* const nodep) override { + const auto& value = m_cfuncsCache(nodep); + // Check whether it was already visited by visitCalledCFunc() + if (value[0].m_state != CFuncCache::UNINITIALIZED + || value[1].m_state != CFuncCache::UNINITIALIZED) { + return; + } + iterateChildren(nodep); + } + // Visit AstCFunc from a AstNodeCCall - this function is rather expensive to call therefore, it + // only shall be called when necessary + void visitCalledCFunc(AstCFunc* const nodep) { + if (!m_inProcess) return; // Skip if not in process - nothing would be added to any set + const size_t cacheKey = static_cast(m_gatherVars); + CFuncCache& value = m_cfuncsCache(nodep)[cacheKey]; + switch (value.m_state) { + case CFuncCache::UNINITIALIZED: { + // Save current state + VL_RESTORER_CLEAR(m_processDomains); + VL_RESTORER_CLEAR(m_writtenBySuspendable); + + // Visit + value.m_state = CFuncCache::VISITING; + m_callStack.push_back(nodep); + iterateChildren(nodep); + m_callStack.pop_back(); + value.m_state = CFuncCache::INITIALIZED; + + // Save a cache + std::swap(m_processDomains, value.m_processDomains); + std::swap(m_writtenBySuspendable, value.m_writtenBySuspendable); + } break; + case CFuncCache::VISITING: { + for (size_t i = m_callStack.size() - 1; m_callStack.at(i) != nodep; --i) { + m_cfuncsCache(m_callStack[i])[cacheKey].m_includes.insert(nodep); + } + return; // Break recursion + } + case CFuncCache::INITIALIZED: break; + } + // Add cached values to the visitor state + // Add included caches recursively + for (AstCFunc* const includedCFuncp : value.m_includes) visitCalledCFunc(includedCFuncp); + m_writtenBySuspendable.insert(value.m_writtenBySuspendable.begin(), + value.m_writtenBySuspendable.end()); + m_notInlinedWritesInSuspendableUsage += value.m_writtenBySuspendable.size(); + m_processDomains.insert(value.m_processDomains.begin(), value.m_processDomains.end()); + } void visit(AstExprStmt* nodep) override { iterateChildren(nodep); } //-------------------- @@ -289,7 +372,10 @@ public: , m_externalDomains{externalDomains} { iterate(nodep); } - ~AwaitVisitor() override = default; + ~AwaitVisitor() override { + V3Stats::addStat("Scheduling, count of non-inlined signal writes in suspendables", + m_notInlinedWritesInSuspendableUsage); + } }; TimingKit prepareTiming(AstNetlist* const netlistp) { diff --git a/test_regress/t/t_sched_noninlined_func_suspendable.py b/test_regress/t/t_sched_noninlined_func_suspendable.py new file mode 100755 index 000000000..7a669948d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_func_suspendable.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-dfg']) + +test.execute() + +test.file_grep(test.stats, + r'Scheduling, count of non-inlined signal writes in suspendables\s+(\d+)', 6) + +test.passes() diff --git a/test_regress/t/t_sched_noninlined_func_suspendable.v b/test_regress/t/t_sched_noninlined_func_suspendable.v new file mode 100644 index 000000000..a7e9c8f0d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_func_suspendable.v @@ -0,0 +1,93 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: off + +package pkg; + bit [2:0] y3; +endpackage + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + + bit [2:0] y2; + bit [2:0] z2; + assign z2[0] = 1'b1; + assign z2[1] = !(y2[0]); + assign z2[2] = !(|y2[1:0]); + + import pkg::y3; + bit [2:0] z3; + assign z3[0] = 1'b1; + assign z3[1] = !(y3[0]); + assign z3[2] = !(|y3[1:0]); + + bit [2:0] y4; + bit [2:0] z4; + assign z4[0] = 1'b1; + assign z4[1] = !(y4[0]); + assign z4[2] = !(|y4[1:0]); + class Foo; + function automatic int bar(); + // verilator no_inline_task + y2 = 3'b111; + y3 = 3'b111; + return 1; + endfunction + task run(); + y = 3'b111; + #1; + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + endtask + task a(bit x = 0); + // verilator no_inline_task + y4 = ~y4; + #1; + if (!x) b(!x); + endtask + task b(bit x = 0); + // verilator no_inline_task + if (!x) a(!x); + endtask + endclass + initial begin + static Foo foo = new; + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b111); + `checkh(z3, 3'b111); + `checkh(z4, 3'b111); + void'(foo.bar()); + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + foo.run(); + foo.a(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b001); + foo.b(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index bff344460..48f633e08 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -78,7 +78,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -110,7 +110,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -171,7 +171,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -247,7 +247,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -278,7 +278,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -339,7 +339,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -463,7 +463,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -498,7 +498,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -581,7 +581,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -676,7 +676,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -711,7 +711,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -794,7 +794,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -831,7 +831,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#} 'act' region trigger index 0 is active: @([event] t.ec.e) --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 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 @@ -906,7 +906,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -936,7 +936,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -994,7 +994,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1060,7 +1060,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1125,7 +1125,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1152,7 +1152,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -1215,7 +1215,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1280,7 +1280,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1320,7 +1320,7 @@ -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#,#} 'act' region trigger index 1 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 @@ -1371,7 +1371,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1427,7 +1427,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1486,7 +1486,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1541,7 +1541,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1596,7 +1596,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 @@ -1649,7 +1649,7 @@ -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 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -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 From 4df63cbeab5cb46aca954a6253d507708b90bc70 Mon Sep 17 00:00:00 2001 From: Igor Zaworski Date: Mon, 6 Jul 2026 17:43:53 +0200 Subject: [PATCH 2/2] [#99211] Fix scheduler lost trigger - virtualization Signed-off-by: Igor Zaworski --- src/CMakeLists.txt | 1 + src/Makefile_obj.in | 1 + src/V3SchedTiming.cpp | 13 +- src/V3Virtual.cpp | 226 ++++++++++++++++++ src/V3Virtual.h | 64 +++++ ..._sched_noninlined_virt_func_suspendable.py | 21 ++ ...t_sched_noninlined_virt_func_suspendable.v | 117 +++++++++ 7 files changed, 441 insertions(+), 2 deletions(-) create mode 100644 src/V3Virtual.cpp create mode 100644 src/V3Virtual.h create mode 100755 test_regress/t/t_sched_noninlined_virt_func_suspendable.py create mode 100644 test_regress/t/t_sched_noninlined_virt_func_suspendable.v diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90a0d3ab4..3c9edefa0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -369,6 +369,7 @@ set(COMMON_SOURCES V3Unroll.cpp V3UnrollGen.cpp V3VariableOrder.cpp + V3Virtual.cpp V3Waiver.cpp V3Width.cpp V3WidthCommit.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index f5a728063..bb6b60a4d 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -348,6 +348,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Unknown.o \ V3Unroll.o \ V3UnrollGen.o \ + V3Virtual.o \ V3Width.o \ V3WidthCommit.o \ V3WidthSel.o \ diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index afce44efd..fb130c0db 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -29,6 +29,7 @@ #include "V3AstUserAllocator.h" #include "V3EmitCBase.h" #include "V3Sched.h" +#include "V3Virtual.h" #include @@ -192,6 +193,8 @@ class AwaitVisitor final : public VNVisitor { AstNodeStmt*& m_postUpdatesr; // Post updates for the trigger eval function // Additional var sensitivities std::map>& m_externalDomains; + std::unique_ptr + m_classGraphp; // class graph to get possibly called functions from a virtual call std::set m_processDomains; // Sentrees from the current process // Variables written by suspendable processes std::set m_writtenBySuspendable; @@ -307,7 +310,12 @@ class AwaitVisitor final : public VNVisitor { void visit(AstNodeCCall* const nodep) override { iterateChildren(nodep); // We need to visit bodies of non-inlined functions - visitCalledCFunc(nodep->funcp()); + const auto& cfuncps = m_classGraphp->getCallPossibleCFuncs(nodep); + if (cfuncps.empty()) { + visitCalledCFunc(nodep->funcp()); + } else { + for (AstCFunc* const cfuncp : cfuncps) visitCalledCFunc(cfuncp); + } } void visit(AstCFunc* const nodep) override { const auto& value = m_cfuncsCache(nodep); @@ -369,7 +377,8 @@ public: : m_scopeTopp{nodep->topScopep()->scopep()} , m_lbs{lbs} , m_postUpdatesr{postUpdatesr} - , m_externalDomains{externalDomains} { + , m_externalDomains{externalDomains} + , m_classGraphp{V3Virtual::buildClassGraph(nodep)} { iterate(nodep); } ~AwaitVisitor() override { diff --git a/src/V3Virtual.cpp b/src/V3Virtual.cpp new file mode 100644 index 000000000..b68877727 --- /dev/null +++ b/src/V3Virtual.cpp @@ -0,0 +1,226 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Virtual function calls - function finder +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Builds a graph of inheritance (implements & extends) +// Allows for lookups of what AstCFunc may be called +// due to a virtual function call +// +// Current limitation: This only may be called after V3Task +// when AstNodeFTasks are substituted with AstCFuncs +// +//************************************************************************* +// TODO: +// - Probably in future this phase shall have a check for overriding +// virtual functions with static functions - currently +// there is no such check - it shall happen before V3Task +// since it moves static functions outside classes +// - This shall check for error that is currently emitted in V3LinkDot +// `Class 'Derived' implements 'Base' but is missing implementation +// for 'function' (IEEE 1800-2023 8.26)` +// Sometimes this error is emitted and it is a false positive +// since the following snipped is correct but Verilator emits an error: +// +// class Base; +// virtual task foo(); +// endtask +// endclass +// +// interface class Iface; +// pure virtual task foo(); +// endclass +// +// class Derived extends Base implements Iface; +// // This is legal as it is but Verilator fails +// endclass +// +// Mentioned `IEEE 1800-2023 8.26` says: +// `When an interface class is implemented by a class, the required +// implementations of interface class methods may be +// provided by inherited virtual method implementations.` +// +// Therefore, this shall probably become a full phase +// and check for those errors somewhere before V3Task +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Virtual.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +class V3ClassGraphVertex final : public V3GraphVertex { + VL_RTTI_IMPL(V3ClassGraphVertex, V3GraphVertex) + friend class ClassGraphBuilderVisitor; + enum Virtualization : uint8_t { + NON_VIRTUAL = 0, // Not a virtual member function + VIRTUAL_IMPLICIT, // virtual keyword is not next to a function definition the ancestor + // class has such virtual function therefore, it is treated as an + // virtual + VIRTUAL, // function explicitly defined as virtual with a keyword + UNDEFINED_VIRTUAL, // Function not declared in a current class however, it is inherited + // and it is virtual + // Note: non-virtual inherited function may not be described with this enum - no need for + // now as well as static functions + }; + struct FuncInfo final { + AstCFunc* const m_cfuncp; // pointer to a described function + std::unordered_set + m_cache; // set of functions that may be called due to a virtual call + Virtualization m_virtualization; // Whether function is virtual (see Virtualization) + bool m_cacheInitialized; // true when m_cache stores a valid value + FuncInfo() + : m_cfuncp{nullptr} + , m_virtualization{UNDEFINED_VIRTUAL} + , m_cacheInitialized{false} {} + FuncInfo(AstCFunc* const cfuncp, Virtualization virtualization) + : m_cfuncp{cfuncp} + , m_virtualization{virtualization} + , m_cacheInitialized{false} {} + }; + std::unordered_map + m_funcsVirtualization; // Map from function names to their description + const AstClass* const m_classp; // A class of which the function is a member of + +public: + explicit V3ClassGraphVertex(V3Graph* const graphp, const AstClass* const classp) + : V3GraphVertex{graphp} + , m_classp{classp} {} + ~V3ClassGraphVertex() override = default; + + std::string name() const override { return m_classp->name(); } + + void addFunc(AstCFunc* const cfuncp) { + m_funcsVirtualization.emplace(cfuncp->name(), + FuncInfo{cfuncp, cfuncp->isVirtual() + ? V3ClassGraphVertex::VIRTUAL + : V3ClassGraphVertex::NON_VIRTUAL}); + } + void propagateVirt(const V3ClassGraphVertex& from) { + for (auto funcs : from.m_funcsVirtualization) { + if (funcs.second.m_virtualization == NON_VIRTUAL) continue; + auto& vertex = m_funcsVirtualization[funcs.first]; + if (vertex.m_virtualization == NON_VIRTUAL) vertex.m_virtualization = VIRTUAL_IMPLICIT; + } + } + const std::unordered_set& getCallPossibleCFuncs(const std::string& name) & { + auto it = m_funcsVirtualization.find(name); + UASSERT(it != m_funcsVirtualization.end(), + "Unexpected call - CFunc with name '" << name << "' not found"); + FuncInfo& info = it->second; + if (!info.m_cacheInitialized) { + if (info.m_cfuncp) info.m_cache.insert(info.m_cfuncp); + if (info.m_virtualization != V3ClassGraphVertex::NON_VIRTUAL) { + for (V3GraphEdge& edge : outEdges()) { + V3ClassGraphVertex* const other = edge.top()->as(); + const std::unordered_set otherCalls + = other->getCallPossibleCFuncs(name); + info.m_cache.insert(otherCalls.begin(), otherCalls.end()); + } + } + info.m_cacheInitialized = true; + } + return info.m_cache; + } +}; + +V3ClassesGraphWrapper::V3ClassesGraphWrapper( + std::unique_ptr graph, + std::unordered_map vertexMap) + : m_graphp{std::move(graph)} + , m_vertexMap{std::move(vertexMap)} {} +V3ClassesGraphWrapper::~V3ClassesGraphWrapper() + = default; // Must be here because only this translation unit actually knows size + // of the V3ClassGraphVertex which is needed for ~unique_ptr() + +const std::unordered_set& +V3ClassesGraphWrapper::getCallPossibleCFuncs(const AstNodeCCall* const callp) const& { + // super references and AstCNew shall have AstNodeCCall::funcp() pointing to the only possible + // AstCFunc that may be called + if (callp->superReference() || VN_IS(callp, CNew)) return m_emptySet; + AstNode* nodep = callp->funcp(); + while (nodep && nodep->backp() + && (nodep->backp()->nextp() == nodep || VN_IS(nodep->backp(), Scope))) { + nodep = nodep->backp(); + } + AstClass* const classp = VN_CAST(nodep->backp(), Class); + if (!classp) return m_emptySet; // Not under class return empty set + auto it = m_vertexMap.find(classp); + UASSERT_OBJ(it != m_vertexMap.end(), classp, "Class not mapped"); + return it->second->getCallPossibleCFuncs(callp->funcp()->name()); +} + +// Build a Class graph and returns it in a V3ClassesGraphWrapper +class ClassGraphBuilderVisitor final : VNVisitor { + std::unique_ptr m_graphp; // Graph of classes + std::unordered_map + m_vertexMap; // Maps classes to their vertices + AstClass* m_classp = nullptr; // Current class + V3ClassGraphVertex* m_classVertexp = nullptr; // vertex corresponding to a current class - + // getClassVertex(m_classp) == m_classVertexp + + V3ClassGraphVertex* getClassVertex(const AstClass* const classp) { + auto it = m_vertexMap.find(classp); + if (it != m_vertexMap.end()) return it->second; + return m_vertexMap.emplace(classp, new V3ClassGraphVertex{m_graphp.get(), classp}) + .first->second; + } + + void visit(AstCFunc* const nodep) override { + if (!m_classp || nodep->isConstructor() || nodep->isDestructor()) return; + UASSERT_OBJ(m_classVertexp, nodep, + "If function is under class m_classVertexp shall be set"); + m_classVertexp->addFunc(nodep); + } + void visit(AstClassExtends* const nodep) override { + UASSERT_OBJ(m_classVertexp, nodep, + "m_classVertexp shall be set while visiting AstClassExtends"); + new V3GraphEdge{m_graphp.get(), + getClassVertex(VN_AS(nodep->childDTypep(), ClassRefDType)->classp()), + m_classVertexp, 1}; + } + void visit(AstNodeModule* const nodep) override { + VL_RESTORER(m_classp); + VL_RESTORER(m_classVertexp); + if ((m_classp = VN_CAST(nodep, Class))) m_classVertexp = getClassVertex(m_classp); + iterateChildren(nodep); + } + void visit(AstNode* const nodep) override { iterateChildren(nodep); } + +public: + explicit ClassGraphBuilderVisitor(AstNetlist* const nodep) + : m_graphp{new V3Graph} { + iterate(nodep); + + // Propagate virtualization + m_graphp->order(); + for (V3GraphVertex& graphVertex : m_graphp->vertices()) { + V3ClassGraphVertex* const vertexp = graphVertex.as(); + for (V3GraphEdge& outEdge : vertexp->outEdges()) { + V3ClassGraphVertex* const successorp = outEdge.top()->as(); + successorp->propagateVirt(*vertexp); + } + } + } + ~ClassGraphBuilderVisitor() override = default; + std::unique_ptr takeGraph() && { + return std::unique_ptr( + new V3ClassesGraphWrapper{std::move(m_graphp), std::move(m_vertexMap)}); + } +}; + +std::unique_ptr V3Virtual::buildClassGraph(AstNetlist* netlistp) { + return ClassGraphBuilderVisitor{netlistp}.takeGraph(); +} diff --git a/src/V3Virtual.h b/src/V3Virtual.h new file mode 100644 index 000000000..6510fa7d5 --- /dev/null +++ b/src/V3Virtual.h @@ -0,0 +1,64 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Virtual function calls - function finder +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3VIRTUAL_H_ +#define VERILATOR_V3VIRTUAL_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Graph.h" + +#include +#include +#include + +//============================================================================ + +class V3ClassGraphVertex; + +// Graph of classes - build by V3Virtual::buildClassGraph() and used to get CFuncs that may be +// called by a AstNodeCCall (due to virtualization) +class V3ClassesGraphWrapper final { + friend class ClassGraphBuilderVisitor; + + const std::unique_ptr m_graphp; // graph of classes + const std::unordered_map + m_vertexMap; // Map from AstClass to a corresponding vertex + const std::unordered_set + m_emptySet; // Always empty set - used to return a reference to an empty set + + V3ClassesGraphWrapper(std::unique_ptr graph, + std::unordered_map vertexMap); + +public: + ~V3ClassesGraphWrapper(); + + // Returns possible CFuncs that may be called due to a NodeCCall passed as a callp argument + // Warning: returned set may be empty if NodeCCall is a New call or a super call - in such + // cases only `callp->funcp()` may be called. Also, if callp is to a non-member function the + // set will be empty - post V3Task static functions are not member functions anymore. + const std::unordered_set& + getCallPossibleCFuncs(const AstNodeCCall* const callp) const&; +}; + +class V3Virtual final { +public: + static std::unique_ptr buildClassGraph(AstNetlist*); +}; + +#endif // Guard diff --git a/test_regress/t/t_sched_noninlined_virt_func_suspendable.py b/test_regress/t/t_sched_noninlined_virt_func_suspendable.py new file mode 100755 index 000000000..b6c85ee2d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_virt_func_suspendable.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-dfg']) + +test.execute() + +test.file_grep(test.stats, + r'Scheduling, count of non-inlined signal writes in suspendables\s+(\d+)', 7) + +test.passes() diff --git a/test_regress/t/t_sched_noninlined_virt_func_suspendable.v b/test_regress/t/t_sched_noninlined_virt_func_suspendable.v new file mode 100644 index 000000000..1d3fe9da6 --- /dev/null +++ b/test_regress/t/t_sched_noninlined_virt_func_suspendable.v @@ -0,0 +1,117 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: off + +class TestBase; + virtual function int bar(); + endfunction +endclass + +interface class D1; + pure virtual task run(); +endclass + +interface class D2 implements D1; + pure virtual task a(bit x = 0); +endclass + +interface class D3 implements D1; + pure virtual task b(bit x = 0); +endclass + +interface class D4 implements D2, D3; +endclass + +package pkg; + bit [2:0] y3; +endpackage + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + + bit [2:0] y2; + bit [2:0] z2; + assign z2[0] = 1'b1; + assign z2[1] = !(y2[0]); + assign z2[2] = !(|y2[1:0]); + + import pkg::y3; + bit [2:0] z3; + assign z3[0] = 1'b1; + assign z3[1] = !(y3[0]); + assign z3[2] = !(|y3[1:0]); + + bit [2:0] y4; + bit [2:0] z4; + assign z4[0] = 1'b1; + assign z4[1] = !(y4[0]); + assign z4[2] = !(|y4[1:0]); + class Foo extends TestBase implements D4; + function automatic int bar(); + // verilator no_inline_task + y2 = 3'b111; + y3 = 3'b111; + return 1; + endfunction + task run(); + y = 3'b111; + #1; + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + endtask + task a(bit x = 0); + // verilator no_inline_task + y4 = ~y4; + #1; + if (!x) b(!x); + endtask + task b(bit x = 0); + // verilator no_inline_task + if (!x) a(!x); + endtask + endclass + initial begin + static Foo inst = new; + static TestBase foo = inst; + static D3 d3 = inst; + static D1 d1 = inst; + static D4 d4 = inst; + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b111); + `checkh(z3, 3'b111); + `checkh(z4, 3'b111); + void'(foo.bar()); + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + d1.run(); + d4.a(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b001); + d3.b(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule