From 83eb54ec7e5b4c438b4a83a51b21161b41538bb9 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 4 Jul 2026 11:05:34 -0400 Subject: [PATCH 01/24] Fix wild compare operators on strings --- src/V3Width.cpp | 6 ++++-- test_regress/t/t_eq_wild.v | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 86d688cc7..a99b293ed 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -9564,9 +9564,11 @@ class WidthVisitor final : public VNVisitor { // No width change on output;... // All below have bool or double outputs switch (nodep->type()) { case VNType::Eq: - case VNType::EqCase: newp = new AstEqN{fl, lhsp, rhsp}; break; + case VNType::EqCase: + case VNType::EqWild: newp = new AstEqN{fl, lhsp, rhsp}; break; case VNType::Neq: - case VNType::NeqCase: newp = new AstNeqN{fl, lhsp, rhsp}; break; + case VNType::NeqCase: + case VNType::NeqWild: newp = new AstNeqN{fl, lhsp, rhsp}; break; case VNType::Gt: case VNType::GtS: newp = new AstGtN{fl, lhsp, rhsp}; break; case VNType::Gte: diff --git a/test_regress/t/t_eq_wild.v b/test_regress/t/t_eq_wild.v index c34463bd1..100726f23 100644 --- a/test_regress/t/t_eq_wild.v +++ b/test_regress/t/t_eq_wild.v @@ -4,7 +4,16 @@ // SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + module t; + localparam string AB = "AB"; + + bit r; + initial begin if (('bxz10 ==? 'bxxx0) !== 1) $stop; if (('bxz10 ==? 'bxxx1) !== 0) $stop; @@ -12,6 +21,16 @@ module t; if (('bxz10 !=? 'bxxx1) !== 1) $stop; if (('bxz10 !=? 'bxxx0) !== 0) $stop; if (('bxz10 !=? 'b1xx0) !== 'x) $stop; + + r = (AB ==? "AA"); + `checkd(r, 0); + r = (AB ==? "AB"); + `checkd(r, 1); + r = (AB !=? "AB"); + `checkd(r, 0); + + // real/shortreal is illegal so not tested here + $write("*-* All Finished *-*\n"); $finish; end From 566f4e6efc7625621c31a9a81983a9289dd82a5a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 4 Jul 2026 17:27:07 +0100 Subject: [PATCH 02/24] Internals: Add new VL_RESTORER flavours (#7866) Restrict VL_RESTORER to be usable only with trivially copyable types. Introduce VL_RESTORER_COPY and VL_RESTORER_CLEAR, which are more efficient versions usable for non trivially copyable types. VL_RESTORER_COPY semantically behaves the same as VL_RESTORER, but only does one copy at initialization, and on scope exit restores via a move. VL_RESTORER_CLEAR swaps the variable with an new one constructed via the no-args constructor (e.g. empty collection), which does not require any copying at any point. Static assertions enforce picking one of the new flavours when copying might be expensive. --- src/V3Assert.cpp | 3 +- src/V3Begin.cpp | 36 ++++++++--------- src/V3Broken.cpp | 9 ++--- src/V3Class.cpp | 4 +- src/V3Coverage.cpp | 18 ++++----- src/V3Covergroup.cpp | 9 ++--- src/V3EmitV.cpp | 3 +- src/V3Fork.cpp | 3 +- src/V3Global.h | 90 ++++++++++++++++++++++++++++++++++++------ src/V3HierBlock.cpp | 9 ++--- src/V3Interface.cpp | 2 +- src/V3LibMap.cpp | 4 +- src/V3LinkCells.cpp | 4 +- src/V3LinkDot.cpp | 50 ++++++++++------------- src/V3LinkParse.cpp | 7 ++-- src/V3LinkWith.cpp | 16 ++++---- src/V3Param.cpp | 8 ++-- src/V3RandSequence.cpp | 10 ++--- src/V3Task.cpp | 3 +- src/V3TraceDecl.cpp | 8 ++-- src/V3Tristate.cpp | 6 +-- src/V3Udp.cpp | 6 +-- src/V3Width.cpp | 2 +- src/V3WidthCommit.cpp | 18 ++++----- 24 files changed, 180 insertions(+), 148 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index ee18b24b4..e7724cfda 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -1153,12 +1153,11 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modp); VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); - VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); VL_RESTORER(m_finalp); + VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; - m_modExpr2Sen2DelayedAlwaysp.clear(); m_finalp = nullptr; iterateChildren(nodep); } diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 2ac71c770..5cce373b0 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -137,12 +137,9 @@ class BeginVisitor final : public VNVisitor { UINFO(8, " rename to " << nodep->name()); m_statep->userMarkChanged(nodep); } - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); - m_displayScope = ""; - m_namedScope = ""; - m_unnamedScope = ""; + VL_RESTORER_CLEAR(m_displayScope); + VL_RESTORER_CLEAR(m_namedScope); + VL_RESTORER_CLEAR(m_unnamedScope); iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { @@ -163,14 +160,13 @@ class BeginVisitor final : public VNVisitor { // naming; so that any begin's inside the function will rename // inside the function. // Process children - VL_RESTORER(m_displayScope); + VL_RESTORER_COPY(m_displayScope); VL_RESTORER(m_ftaskp); VL_RESTORER(m_liftedp); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_CLEAR(m_namedScope); + VL_RESTORER_CLEAR(m_unnamedScope); m_displayScope = dot(m_displayScope, nodep->name()); - m_namedScope = ""; - m_unnamedScope = ""; + m_ftaskp = nodep; m_liftedp = nullptr; iterateChildren(nodep); @@ -191,9 +187,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstGenBlock* nodep) override { // GenBlocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); UASSERT_OBJ(!m_keepBegins, nodep, "Should be able to eliminate all AstGenBlock"); dotNames(nodep->name(), nodep->fileline(), "__BEGIN__"); iterateAndNextNull(nodep->itemsp()); @@ -227,9 +223,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstBegin* nodep) override { // Begin blocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); { VL_RESTORER(m_keepBegins); m_keepBegins = false; @@ -261,9 +257,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstNodeBlock* nodep) override { // Begin/Fork blocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); { VL_RESTORER(m_keepBegins); m_keepBegins = VN_IS(nodep, Fork); diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index fab0077cc..ba5aa0135 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -252,18 +252,15 @@ private: void visit(AstScope* nodep) override { VL_RESTORER(m_inScope); m_inScope = true; - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeModule* nodep) override { - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeUOrStructDType* nodep) override { - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeVarRef* nodep) override { diff --git a/src/V3Class.cpp b/src/V3Class.cpp index 43f417313..2ccc1044b 100644 --- a/src/V3Class.cpp +++ b/src/V3Class.cpp @@ -114,7 +114,7 @@ class ClassVisitor final : public VNVisitor { classScopep->aboveScopep(), classScopep->aboveCellp()}; packagep->addStmtsp(scopep); // Iterate - VL_RESTORER(m_prefix); + VL_RESTORER_CLEAR(m_prefix); VL_RESTORER(m_classPackagep); VL_RESTORER(m_classScopep); VL_RESTORER(m_packageScopep); @@ -129,7 +129,7 @@ class ClassVisitor final : public VNVisitor { void visit(AstNodeModule* nodep) override { // Visit for NodeModules that are not AstClass (AstClass is-a AstNodeModule) // Classes are always under a Package (perhaps $unit) or a module - VL_RESTORER(m_prefix); + VL_RESTORER_CLEAR(m_prefix); VL_RESTORER(m_modp); m_modp = nodep; m_prefix = nodep->name() + "__03a__03a"; // :: diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 9c19319d8..9db38cb6e 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -279,8 +279,8 @@ class CoverageVisitor final : public VNVisitor { const AstNodeModule* const origModp = m_modp; VL_RESTORER(m_modp); VL_RESTORER(m_state); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); createHandle(nodep); m_modp = nodep; m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops @@ -294,8 +294,8 @@ class CoverageVisitor final : public VNVisitor { void visit(AstClass* nodep) override { VL_RESTORER(m_modp); VL_RESTORER(m_state); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); createHandle(nodep); m_modp = nodep; // Covergroup declarations are not executable statements; suppress line/expr/toggle @@ -356,8 +356,8 @@ class CoverageVisitor final : public VNVisitor { void visit(AstNodeFTask* nodep) override { VL_RESTORER(m_ftaskp); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); m_ftaskp = nodep; if (!nodep->dpiImport()) iterateProcedure(nodep); } @@ -762,7 +762,7 @@ class CoverageVisitor final : public VNVisitor { } void visit(AstGenBlock* nodep) override { // Similar to AstBegin - VL_RESTORER(m_beginHier); + VL_RESTORER_COPY(m_beginHier); if (nodep->name() != "") { m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name(); } @@ -776,7 +776,7 @@ class CoverageVisitor final : public VNVisitor { // generate blocks; each point should get separate consideration. // (Currently ignored for line coverage, since any generate iteration // covers the code in that line.) - VL_RESTORER(m_beginHier); + VL_RESTORER_COPY(m_beginHier); VL_RESTORER(m_inToggleOff); VL_RESTORER(m_exprStmtsp); m_exprStmtsp = nodep; @@ -874,7 +874,7 @@ class CoverageVisitor final : public VNVisitor { UASSERT_OBJ(m_exprs.empty(), nodep, "unexpected expression coverage garbage"); VL_RESTORER(m_seeking); VL_RESTORER(m_objective); - VL_RESTORER(m_exprs); + VL_RESTORER_CLEAR(m_exprs); // Already asserted above it's empty. m_seeking = SEEKING; m_objective = false; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index b1890e001..f243b0c33 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -1727,15 +1727,12 @@ class FunctionalCoverageVisitor final : public VNVisitor { VL_RESTORER(m_covergroupp); VL_RESTORER(m_sampleFuncp); VL_RESTORER(m_constructorp); - VL_RESTORER(m_coverpoints); - VL_RESTORER(m_coverpointMap); - VL_RESTORER(m_coverCrosses); + VL_RESTORER_CLEAR(m_coverpoints); + VL_RESTORER_CLEAR(m_coverpointMap); + VL_RESTORER_CLEAR(m_coverCrosses); m_covergroupp = nodep; m_sampleFuncp = nullptr; m_constructorp = nullptr; - m_coverpoints.clear(); - m_coverpointMap.clear(); - m_coverCrosses.clear(); // Extract and store the clocking event from AstCovergroup node // The parser creates this node to preserve the event information diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index db0798e38..405204084 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1050,8 +1050,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { if (nodep->packed()) puts("packed "); { puts("{\n"); - VL_RESTORER(m_packedps); - m_packedps.clear(); + VL_RESTORER_CLEAR(m_packedps); for (AstMemberDType* itemp = nodep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { iterateConst(itemp); diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 0071d8f75..ee02c0781 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -561,10 +561,9 @@ class ForkVisitor final : public VNVisitor { // replace body with a call to that task. Returns true iff wrapped. bool taskify(AstBegin* beginp) { // Visit statement to gather variables (And recursively process) - VL_RESTORER(m_forkLocalsp); + VL_RESTORER_CLEAR(m_forkLocalsp); VL_RESTORER(m_capturedVarsp); VL_RESTORER(m_capturedArgsp); - m_forkLocalsp.clear(); m_capturedVarsp = nullptr; m_capturedArgsp = nullptr; iterate(beginp); diff --git a/src/V3Global.h b/src/V3Global.h index 4825a540a..08f9db3a7 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -44,27 +45,90 @@ class V3ThreadPool; //====================================================================== // Restorer -/// Save a given variable's value on the stack, restoring it at end-of-scope. -// Object must be named, or it will not persist until end-of-scope. -// Constructor needs () or GCC 4.8 false warning. -#define VL_RESTORER(var) const VRestorer> restorer_##var(var); -/// Get the copy of the variable previously saved by VL_RESTORER() +// For all flavours, 'var' must be a named scalar variable (simple identifier). + +// Copy given variable's value on the stack, copy saved value back at end-of-scope. +// Only usable for trivially copyable types; use VL_RESTORER_COPY/VL_RESTORER_CLEAR for +// non-trivially copyable types, which are more efficient. +#define VL_RESTORER(var) \ + const VRestorerTrivial> restorer_##var(var); + +// This is the equivalent of VL_RESTORER for non-trivially copyable types. Still more efficient +// than VL_RESTORER as uses move to restore. +#define VL_RESTORER_COPY(var) \ + const VRestorerCopy> restorer_##var(var); + +// Swap 'var' with a no-args constructed object of the same type, effectively clearing it, then +// swap back at end-of-scope. No copying involved. Use this e.g. for std containers where they +// would be .clear()'d right after the VL_RESTORER instance. +#define VL_RESTORER_CLEAR(var) \ + const VRestorerClear> restorer_##var(var); + +// Get const reference to the saved copy #define VL_RESTORER_PREV(var) restorer_##var.saved() -// Object used by VL_RESTORER. This object must be an auto variable, not -// allocated on the heap or otherwise. +// Implementation of VL_RESTORER template -class VRestorer final { +class VRestorerTrivial final { + static_assert(std::is_trivially_copyable::value, + "Use VL_RESTORER_{COPY,CLEAR} for non trivially copyable types"); T& m_ref; // Reference to object we're saving and restoring const T m_saved; // Value saved, for later restore public: - explicit VRestorer(T& permr) - : m_ref{permr} - , m_saved{permr} {} - ~VRestorer() { m_ref = m_saved; } + explicit VRestorerTrivial(T& val) + : m_ref{val} + , m_saved{val} {} + ~VRestorerTrivial() { m_ref = m_saved; } + VL_UNCOPYABLE(VRestorerTrivial); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + + const T& saved() const { return m_saved; } +}; + +// Implementation of VL_RESTORER_COPY +template +class VRestorerCopy final { + static_assert(!std::is_trivially_copyable::value, + "Use VL_RESTORER for trivially copyable types"); + T& m_ref; // Reference to object we're saving and restoring + T m_saved; // Value saved, for later restore + +public: + explicit VRestorerCopy(T& val) + : m_ref{val} + , m_saved{val} {} + ~VRestorerCopy() { m_ref = std::move(m_saved); } + VL_UNCOPYABLE(VRestorerCopy); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + + const T& saved() const { return m_saved; } +}; + +// Implementation of VL_RESTORER_CLEAR. +template +class VRestorerClear final { + static_assert(!std::is_trivially_copyable::value, + "Use VL_RESTORER for trivially copyable types"); + T& m_ref; // Reference to object we're saving and restoring + T m_saved{}; // Owns the swapped-out value; starts empty + +public: + explicit VRestorerClear(T& val) + : m_ref{val} { + std::swap(m_ref, m_saved); + } + ~VRestorerClear() { std::swap(m_ref, m_saved); } + VL_UNCOPYABLE(VRestorerClear); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + const T& saved() const { return m_saved; } - VL_UNCOPYABLE(VRestorer); }; //###################################################################### diff --git a/src/V3HierBlock.cpp b/src/V3HierBlock.cpp index 79bd3667c..8a77354ce 100644 --- a/src/V3HierBlock.cpp +++ b/src/V3HierBlock.cpp @@ -331,12 +331,9 @@ class HierBlockUsageCollectVisitor final : public VNVisitorConst { } // This is a hierarchical block, gather parts - VL_RESTORER(m_params); - VL_RESTORER(m_typeParams); - VL_RESTORER(m_childrenp); - m_params.clear(); - m_typeParams.clear(); - m_childrenp.clear(); + VL_RESTORER_CLEAR(m_params); + VL_RESTORER_CLEAR(m_typeParams); + VL_RESTORER_CLEAR(m_childrenp); iterateChildrenConst(nodep); // Create the graph vertex for this hier block V3HierBlock* const blockp = new V3HierBlock{m_graphp, nodep, m_params, m_typeParams}; diff --git a/src/V3Interface.cpp b/src/V3Interface.cpp index 51cc2d11e..0d5fe47fb 100644 --- a/src/V3Interface.cpp +++ b/src/V3Interface.cpp @@ -43,7 +43,7 @@ class InlineIntfRefVisitor final : public VNVisitor { // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep->topModulep()); } void visit(AstCell* nodep) override { - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); if (m_scope.empty()) { m_scope = nodep->name(); } else { diff --git a/src/V3LibMap.cpp b/src/V3LibMap.cpp index 5a2e153b3..619073551 100644 --- a/src/V3LibMap.cpp +++ b/src/V3LibMap.cpp @@ -47,8 +47,8 @@ class LibMapVisitor final : public VNVisitor { } void visit(AstLibrary* nodep) override { - VL_RESTORER(m_lib); - VL_RESTORER(m_rel); + VL_RESTORER_CLEAR(m_lib); + VL_RESTORER_CLEAR(m_rel); m_lib = nodep->name(); m_rel = V3Os::filenameDir(nodep->fileline()->filename()); iterateChildren(nodep); diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 4393a6955..6c4c637a0 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -157,11 +157,11 @@ class LinkConfigsVisitor final : public VNVisitor { m_isDefault = true; iterateAndNextNull(nodep->usep()); } else if (nodep->isCell()) { - VL_RESTORER(m_cell); + VL_RESTORER_CLEAR(m_cell); m_cell = nodep->cellp()->name(); iterateAndNextNull(nodep->usep()); } else { - VL_RESTORER(m_hierInst); + VL_RESTORER_COPY(m_hierInst); { VL_RESTORER(m_dotp); m_dotp = VN_AS(nodep->cellp(), Dot); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 33e2d38b5..0bb78f8e8 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1259,7 +1259,7 @@ class LinkDotFindVisitor final : public VNVisitor { const bool standalonePkg = !m_modSymp && (m_statep->forPrearray() && VN_IS(nodep, Package)); const bool doit = (m_modSymp || standalonePkg); - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_classOrPackagep); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); @@ -1335,7 +1335,7 @@ class LinkDotFindVisitor final : public VNVisitor { void visit(AstClass* nodep) override { // FindVisitor:: UASSERT_OBJ(m_curSymp, nodep, "Class not under module/package/$unit"); UINFO(8, " " << nodep); - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_classOrPackagep); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); @@ -1384,7 +1384,7 @@ class LinkDotFindVisitor final : public VNVisitor { if (nodep->recursive() && m_inRecursion) return; iterateChildren(nodep); // Recurse in, preserving state - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); VL_RESTORER(m_paramNum); @@ -3584,7 +3584,7 @@ class LinkDotResolveVisitor final : public VNVisitor { void symIterateChildren(AstNode* nodep, VSymEnt* symp) { // Iterate children, changing to given context, with restore to old context - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_curSymp); m_curSymp = symp; m_ds.init(m_curSymp); @@ -3592,7 +3592,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } void symIterateNull(AstNode* nodep, VSymEnt* symp) { // Iterate node, changing to given context, with restore to old context - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_curSymp); m_curSymp = symp; m_ds.init(m_curSymp); @@ -3805,10 +3805,8 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); checkNoDot(nodep); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(nodep->modp(), nodep, "Instance has unlinked module"); // V3LinkCell should have errored out VL_RESTORER(m_cellp); @@ -3845,10 +3843,8 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); // Can be under dot if called as package::class and that class resolves, so no checkNoDot - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(nodep->classp(), nodep, "ClassRef has unlinked class"); UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp() || V3Error::errorCount(), nodep, "class reference parameter not removed by V3Param"); @@ -4753,14 +4749,12 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(8, indent() << "visit " << nodep); UINFO(9, indent() << m_ds.ascii()); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep, "class reference parameter not removed by V3Param"); { - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_pinSymp); if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") { @@ -4834,7 +4828,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } } VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); iterateChildren(nodep); } @@ -5054,7 +5048,7 @@ class LinkDotResolveVisitor final : public VNVisitor { // Created here so should already be resolved. LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_randSymp); VL_RESTORER(m_randMethodCallp); { @@ -5509,7 +5503,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); { VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); if (nodep->name() != "") { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp)); @@ -5524,7 +5518,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); { VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); if (nodep->name() != "") { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp)); @@ -5643,7 +5637,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); VL_RESTORER(m_curSymp); VL_RESTORER(m_currentWithp); - VL_RESTORER(m_restrictedNamesUsed); + VL_RESTORER_COPY(m_restrictedNamesUsed); { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); m_currentWithp = nodep; @@ -5813,7 +5807,7 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_RESTORER(m_curSymp); VL_RESTORER(m_modSymp); VL_RESTORER(m_modp); - VL_RESTORER(m_ifClassImpNames); + VL_RESTORER_COPY(m_ifClassImpNames); VL_RESTORER(m_insideClassExtParam); { m_ds.init(m_curSymp); @@ -6169,7 +6163,7 @@ class LinkDotResolveVisitor final : public VNVisitor { void visit(AstDisable* nodep) override { LINKDOT_VISIT_START(); checkNoDot(nodep); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); m_ds.init(m_curSymp); m_ds.m_dotPos = DP_FIRST; m_ds.m_disablep = nodep; @@ -6249,10 +6243,8 @@ class LinkDotResolveVisitor final : public VNVisitor { UASSERT_OBJ(ifacep, nodep, "Port parameters of AstIfaceRefDType without ifacep()"); if (ifacep->dead()) return; checkNoDot(nodep); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); VL_RESTORER(m_pinSymp); m_pinSymp = m_statep->getNodeSym(ifacep); iterateAndNextNull(nodep->paramsp()); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 5403d77c2..3e3565842 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -273,7 +273,7 @@ class LinkParseVisitor final : public VNVisitor { << nodep->verilogKwd() << "'"); } - VL_RESTORER(m_portDups); + VL_RESTORER_COPY(m_portDups); collectPorts(nodep->stmtsp()); iterateChildren(nodep); @@ -763,12 +763,12 @@ class LinkParseVisitor final : public VNVisitor { VL_RESTORER(m_genblkAbove); VL_RESTORER(m_genblkNum); VL_RESTORER(m_beginDepth); - VL_RESTORER(m_implTypedef); VL_RESTORER(m_lifetime); VL_RESTORER(m_lifetimeAllowed); VL_RESTORER(m_moduleWithGenericIface); VL_RESTORER(m_randSequenceNum); VL_RESTORER(m_valueModp); + VL_RESTORER_CLEAR(m_implTypedef); // Module: Create sim table for entire module and iterate cleanFileline(nodep); @@ -780,7 +780,6 @@ class LinkParseVisitor final : public VNVisitor { m_genblkAbove = 0; m_genblkNum = 0; m_beginDepth = 0; - m_implTypedef.clear(); m_valueModp = nodep; m_lifetime = nodep->lifetime().makeImplicit(); m_lifetimeAllowed = VN_IS(nodep, Class); @@ -797,7 +796,7 @@ class LinkParseVisitor final : public VNVisitor { "Verilator top-level internals"); } - VL_RESTORER(m_portDups); + VL_RESTORER_COPY(m_portDups); collectPorts(nodep->stmtsp()); iterateChildren(nodep); diff --git a/src/V3LinkWith.cpp b/src/V3LinkWith.cpp index 8ea84a3f4..f93d8d589 100644 --- a/src/V3LinkWith.cpp +++ b/src/V3LinkWith.cpp @@ -32,7 +32,7 @@ class LinkWithVisitor final : public VNVisitor { // STATE // Below state needs to be preserved between each module call. - string m_randcIllegalWhy; // Why randc illegal + const char* m_randcIllegalWhyp = nullptr; // Why randc illegal AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) @@ -49,24 +49,24 @@ class LinkWithVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstConstraintBefore* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; + m_randcIllegalWhyp = "'solve before' (IEEE 1800-2023 18.5.9)"; m_randcIllegalp = nodep; iterateChildrenConst(nodep); } void visit(AstDist* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; + m_randcIllegalWhyp = "'constraint dist' (IEEE 1800-2023 18.5.3)"; m_randcIllegalp = nodep; iterateChildrenConst(nodep); } void visit(AstConstraintExpr* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); if (nodep->isSoft()) { - m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; + m_randcIllegalWhyp = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; m_randcIllegalp = nodep; } iterateChildrenConst(nodep); @@ -76,7 +76,7 @@ class LinkWithVisitor final : public VNVisitor { if (nodep->varp()) { // Else due to dead code, might not have var pointer if (nodep->varp()->isRandC() && m_randcIllegalp) { nodep->v3error("Randc variables not allowed in " - << m_randcIllegalWhy << '\n' + << m_randcIllegalWhyp << '\n' << nodep->warnContextPrimary() << '\n' << m_randcIllegalp->warnOther() << "... Location of restricting expression\n" diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 0cf109211..72c33a6e7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2571,11 +2571,9 @@ class ParamVisitor final : public VNVisitor { // Iterate the body { VL_RESTORER(m_modp); - VL_RESTORER(m_ifacePortNames); - VL_RESTORER(m_ifaceInstCells); + VL_RESTORER_CLEAR(m_ifacePortNames); + VL_RESTORER_CLEAR(m_ifaceInstCells); m_modp = modp; - m_ifacePortNames.clear(); - m_ifaceInstCells.clear(); iterateChildren(modp); } } @@ -3241,7 +3239,7 @@ class ParamVisitor final : public VNVisitor { // Note this clears nodep->genforp(), so begin is no longer special } } else { - VL_RESTORER(m_generateHierName); + VL_RESTORER_COPY(m_generateHierName); m_generateHierName += "." + nodep->prettyName(); iterateChildren(nodep); } diff --git a/src/V3RandSequence.cpp b/src/V3RandSequence.cpp index f8f6b155c..b56215bac 100644 --- a/src/V3RandSequence.cpp +++ b/src/V3RandSequence.cpp @@ -136,7 +136,7 @@ class RandSequenceVisitor final : public VNVisitor { m_modp->addStmtsp(taskp); // Create local (not output) break variable - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizeRemaps); AstVar* breakVarp = newBreakVar(nodep->fileline(), false); taskp->addStmtsp(breakVarp); @@ -417,9 +417,9 @@ class RandSequenceVisitor final : public VNVisitor { m_rsp = nodep; VL_RESTORER(m_startProdp); - VL_RESTORER(m_localizes); - VL_RESTORER(m_localizeNames); - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizes); + VL_RESTORER_COPY(m_localizeNames); + VL_RESTORER_COPY(m_localizeRemaps); findLocalizes(nodep); // Find first production @@ -482,7 +482,7 @@ class RandSequenceVisitor final : public VNVisitor { // TODO we could do this only if break exists in the downstream production, // but as-is we'll optimize it away in most cases anyways VL_RESTORER(m_breakVarp); - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizeRemaps); m_breakVarp = newBreakVar(nodep->fileline(), true); m_prodFuncp->addStmtsp(m_breakVarp); diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 0db8d04d3..72e779d1e 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -306,10 +306,9 @@ private: } void visit(AstClass* nodep) override { // Move initial statements into the constructor - VL_RESTORER(m_initialps); + VL_RESTORER_CLEAR(m_initialps); VL_RESTORER(m_ctorp); VL_RESTORER(m_classp); - m_initialps.clear(); m_ctorp = nullptr; m_classp = nodep; { // Find m_initialps, m_ctor diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 1a1ad6b89..29c625f94 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -485,7 +485,7 @@ class TraceDeclVisitor final : public VNVisitor { AstNodeDType* const skipTypep = nodep->skipRefp(); // offset and direction args added in EmitCImp std::string callArgs{"tracep, \"" + VIdProtect::protect(m_traName) + "\""}; - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = skipTypep->fileline(); const DtypeFuncKey dtypeKey{skipTypep, m_traVscp->varp()->varType()}; @@ -530,7 +530,7 @@ class TraceDeclVisitor final : public VNVisitor { void declUnpackedArray(AstUnpackArrayDType* const nodep, bool newFunc) { string prefixName(newFunc ? "name" : m_traName); - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_UNPACKED, @@ -568,7 +568,7 @@ class TraceDeclVisitor final : public VNVisitor { string prefixName(newFunc ? "name" : m_traName); AstNodeDType* const subtypep = nodep->subDTypep()->skipRefToEnump(); - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_PACKED, @@ -918,7 +918,7 @@ class TraceDeclVisitor final : public VNVisitor { return; } - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); int nMembers = 0; diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 5bc72076b..226c5138e 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -2191,8 +2191,8 @@ class TristateVisitor final : public TristateBaseVisitor { VL_RESTORER(m_modp); VL_RESTORER(m_graphing); VL_RESTORER(m_unique); - VL_RESTORER(m_lhsmap); - VL_RESTORER(m_assigns); + VL_RESTORER_CLEAR(m_lhsmap); + VL_RESTORER_CLEAR(m_assigns); // Not preserved, needs pointer instead: TristateGraph origTgraph = m_tgraph; UASSERT_OBJ(m_tgraph.empty(), nodep, "Unsupported: NodeModule under NodeModule"); @@ -2201,8 +2201,6 @@ class TristateVisitor final : public TristateBaseVisitor { m_tgraph.clearAndCheck(); m_unique = 0; m_logicp = nullptr; - m_lhsmap.clear(); - m_assigns.clear(); m_modp = nodep; // Walk the graph, finding all variables and tristate constructs m_graphing = true; diff --git a/src/V3Udp.cpp b/src/V3Udp.cpp index 7234061ed..2ee5908bb 100644 --- a/src/V3Udp.cpp +++ b/src/V3Udp.cpp @@ -56,14 +56,12 @@ class UdpVisitor final : public VNVisitor { VL_RESTORER(m_primp); VL_RESTORER(m_outputInitVerfp); VL_RESTORER(m_isFirstOutput); - VL_RESTORER(m_inputVars); - VL_RESTORER(m_outputVars); + VL_RESTORER_CLEAR(m_inputVars); + VL_RESTORER_CLEAR(m_outputVars); m_outputInitVerfp = nullptr; m_primp = nodep; m_isFirstOutput = false; iterateChildren(nodep); - m_inputVars.clear(); - m_outputVars.clear(); } void visit(AstVar* nodep) override { // Push the input and output vars for primitive. diff --git a/src/V3Width.cpp b/src/V3Width.cpp index a99b293ed..acc20248d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -7852,7 +7852,7 @@ class WidthVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { assertAtStatement(nodep); - VL_RESTORER(m_insideTempNames); + VL_RESTORER_COPY(m_insideTempNames); if (AstClass* const classp = VN_CAST(nodep, Class)) { visitClass(classp); } else { diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index 651bff963..900474423 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -47,7 +47,7 @@ class WidthCommitVisitor final : public VNVisitor { // STATE AstNodeFTask* m_ftaskp = nullptr; // Current function/task AstNodeModule* m_modp = nullptr; // Current module - std::string m_contNba; // In continuous- or non-blocking assignment + const char* m_contNbap = nullptr; // In continuous- or non-blocking assignment bool m_contReads = false; // Check read continuous automatic variables bool m_dynsizedelem = false; // Writing dynamically-sized array element, not the array itself VMemberMap m_memberMap; // Member names cached for fast lookup @@ -148,7 +148,7 @@ private: void varLifetimeCheck(AstNode* nodep, AstVar* varp) { // Skip if we are under a member select (lhs of a dot) // We don't care about lifetime of anything else than rhs of a dot - if (!m_underSel && !m_contNba.empty()) { + if (!m_underSel && m_contNbap) { std::string varType; const AstNodeDType* const varDtp = varp->dtypep()->skipRefp(); if (varp->lifetime().isAutomatic() && !VN_IS(varDtp, IfaceRefDType) @@ -162,7 +162,7 @@ private: if (!varType.empty()) { UINFO(1, " Related var dtype: " << varDtp); nodep->v3error(varType - << " variable not allowed in " << m_contNba + << " variable not allowed in " << m_contNbap << " assignment (IEEE 1800-2023 6.21): " << varp->prettyNameQ()); } } @@ -419,9 +419,9 @@ private: void visit(AstAssignCont* nodep) override { iterateAndNextNull(nodep->timingControlp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "continuous"; + m_contNbap = "continuous"; m_contReads = true; iterateAndNextNull(nodep->lhsp()); iterateAndNextNull(nodep->rhsp()); @@ -432,9 +432,9 @@ private: iterateAndNextNull(nodep->timingControlp()); iterateAndNextNull(nodep->rhsp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "nonblocking"; + m_contNbap = "nonblocking"; m_contReads = false; iterateAndNextNull(nodep->lhsp()); } @@ -444,9 +444,9 @@ private: iterateAndNextNull(nodep->timingControlp()); iterateAndNextNull(nodep->rhsp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "continuous"; + m_contNbap = "continuous"; m_contReads = false; iterateAndNextNull(nodep->lhsp()); } From 1bab793c75175e50cc960cca5bc337cca06c03b1 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 4 Jul 2026 22:45:34 +0100 Subject: [PATCH 03/24] CI: Fix deleting pr notifications after multiple reruns --- ci/ci-pages-notify.bash | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ci/ci-pages-notify.bash b/ci/ci-pages-notify.bash index e5dd599b6..67fa85dda 100755 --- a/ci/ci-pages-notify.bash +++ b/ci/ci-pages-notify.bash @@ -34,9 +34,14 @@ for RUN_ID in ${PR_RUN_IDS//,/ }; do cat ${ARTIFACTS_DIR}/body.txt gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt - # Get the artifact ID - ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') + # Get the artifact IDs. Note there can be more than one artifact named + # 'pr-notification' for a single run, as the artifacts endpoint lists + # artifacts across all run attempts, and a re-run uploads a new one while + # keeping the previous attempt's artifact. + ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') - # Delete it, so we only notify once - gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" + # Delete them all, so we only notify once + for ARTIFACT_ID in ${ARTIFACT_IDS}; do + gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" + done done From 2285e5b6dac6ab271fbe5e5b3d4bdf615bf0104d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 09:24:01 +0100 Subject: [PATCH 04/24] Optimize more always blocks in Dfg (#7775) Optimize always blocks that: - drive an unused variable (so logic can be pruned) - write only a single variable (regardless of CFG size) --- src/V3DfgPasses.cpp | 8 +++++++- src/V3DfgSynthesize.cpp | 11 +++++++---- src/V3DfgVertices.h | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index afca00589..7fc4b4100 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -78,7 +78,13 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { && !vVtxp->hasExtWrRefs() // && !vVtxp->hasModWrRefs(); VL_DO_DANGLING(vVtxp->unlinkDelete(dfg), vVtxp); - if (srcp) VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp); + if (srcp) { + srcp->foreachSource([&](DfgVertex& src) { + src.as()->setDrivesUnusedVars(); + return false; + }); + VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp); + } if (delAst) { VL_DO_DANGLING(vscp->unlinkFrBack()->deleteTree(), vscp); ++ctx.m_varsDeleted; diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index c0f5bdc99..a23b19c4b 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -2005,6 +2005,11 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) { for (DfgVertex& vtx : dfg.opVertices()) { DfgLogic* const logicp = vtx.cast(); if (!logicp) continue; + // If drives an unused variable, synthesize it so the partial logic can be removed + if (logicp->drivesUnusedVars()) { + worklist.push_front(*logicp); + continue; + } // Blocks corresponding to continuous assignments if (logicp->nodep()->keyword() == VAlwaysKwd::CONT_ASSIGN) { worklist.push_front(*logicp); @@ -2016,10 +2021,8 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) { worklist.push_front(*logicp); continue; } - // Simple blocks driving exactly 1 variable, e.g if (rst) a = b else a = c; - if (!logicp->hasMultipleSinks() && cfg.nBlocks() <= 4 && cfg.nEdges() <= 4) { - worklist.push_front(*logicp); - } + // Blocks driving exactly 1 variable + if (!logicp->hasMultipleSinks()) worklist.push_front(*logicp); } // Now expand to cover all logic driving the same set of variables and mark diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index d932de1a3..dd51a58a9 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -512,6 +512,7 @@ class DfgLogic final : public DfgVertexVariadic { AstScope* const m_scopep; // The AstScope m_nodep is under, iff scoped const std::unique_ptr m_cfgp; std::vector m_synth; // Vertices this logic was synthesized into + bool m_drivesUnusedVars = false; // Logic drives unused variables bool m_selectedForSynthesis = false; // Logic selected for synthesis bool m_nonSynthesizable = false; // Logic is not synthesizeable (by DfgSynthesis) bool m_reverted = false; // Logic was synthesized (in part if non-synthesizable) then reverted @@ -538,6 +539,8 @@ public: const CfgGraph& cfg() const { return *m_cfgp; } std::vector& synth() { return m_synth; } const std::vector& synth() const { return m_synth; } + bool drivesUnusedVars() const { return m_drivesUnusedVars; } + void setDrivesUnusedVars() { m_drivesUnusedVars = true; } bool selectedForSynthesis() const { return m_selectedForSynthesis; } void setSelectedForSynthesis() { m_selectedForSynthesis = true; } bool nonSynthesizable() const { return m_nonSynthesizable; } From 13236822d348b83690c79c4204275650b0445eaa Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 09:24:33 +0100 Subject: [PATCH 05/24] CI: Use faster zstd for repo archives (#7868) --- .github/workflows/coverage.yml | 2 +- .github/workflows/reusable-build.yml | 7 +++++-- .github/workflows/reusable-rtlmeter-build.yml | 7 +++++-- .github/workflows/reusable-rtlmeter-run.yml | 4 ++-- .github/workflows/reusable-test.yml | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cc6b463b0..38d4c9a50 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -121,7 +121,7 @@ jobs: - name: Unpack repository archive run: | - tar -x -z -f ${{ needs.build.outputs.archive }} + tar --zstd -x -f ${{ needs.build.outputs.archive }} ls -lsha - name: Download code coverage data diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 87a4052d7..f19fa4ad1 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -88,8 +88,9 @@ jobs: working-directory: ${{ github.workspace }} run: | # Name of the archive must be unique based on the build parameters - ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.gz - tar --posix -c -z -f $ARCHIVE repo + ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.zst + # zstd compresses faster than gzip and decompresses far faster in the many downstream test jobs + ZSTD_NBTHREADS=0 tar --posix --zstd -c -f $ARCHIVE repo echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - name: Upload repository archive @@ -97,3 +98,5 @@ jobs: with: path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} + # Archive is already zstd-compressed; skip upload-artifact's zip pass + compression-level: 0 diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml index b38ed62cf..1c0078207 100644 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ b/.github/workflows/reusable-rtlmeter-build.yml @@ -84,8 +84,9 @@ jobs: id: create-archive run: | SHA=$(git -C repo rev-parse HEAD) - ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.gz - tar --posix -c -z -f $ARCHIVE install + ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.zst + # zstd compresses faster than gzip and decompresses far faster in the downstream run jobs + ZSTD_NBTHREADS=0 tar --posix --zstd -c -f $ARCHIVE install echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - name: Upload Verilator installation archive @@ -94,3 +95,5 @@ jobs: path: ${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} overwrite: true + # Archive is already zstd-compressed; skip upload-artifact's zip pass + compression-level: 0 diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index ab66647db..c4dfbf913 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -101,7 +101,7 @@ jobs: - name: Unpack Verilator installation archive - new run: | - tar -x -z -f ${{ inputs.verilator-archive-new }} + tar --zstd -x -f ${{ inputs.verilator-archive-new }} mv install verilator-new - name: Compile cases - new @@ -164,7 +164,7 @@ jobs: - name: Unpack Verilator installation archive - old if: ${{ inputs.verilator-archive-old != '' }} run: | - tar -x -z -f ${{ inputs.verilator-archive-old }} + tar --zstd -x -f ${{ inputs.verilator-archive-old }} mv install verilator-old - name: Compile cases - old diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index ffec26b84..b36134108 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -63,7 +63,7 @@ jobs: - name: Unpack repository archive working-directory: ${{ github.workspace }} run: | - tar -x -z -f ${{ inputs.archive }} + tar --zstd -x -f ${{ inputs.archive }} ls -lsha - name: Cache $CCACHE_DIR From a518efbedbe789da76271bd4b613440c45bd40b4 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 11:40:08 +0100 Subject: [PATCH 06/24] Internals: Prepare V3DfgBreakCycles to support multi-dimensional arrays (#7867) This is prep for more array support later. Minor functional improvement for tracing arrays, but no meaningful change. --- src/V3DfgBreakCycles.cpp | 560 +++++++++++++++++---------- test_regress/t/t_dfg_break_cycles.py | 1 + test_regress/t/t_dfg_break_cycles.v | 13 + 3 files changed, 363 insertions(+), 211 deletions(-) diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 4bab8858d..c1e3b28c5 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -173,6 +173,7 @@ class TraceDriver final : public DfgVisitor { uint64_t m_component = 0; uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex uint32_t m_msb = 0; // MSB to extract from the currently visited Vertex + std::vector m_idxs; // Indices to extract from the currently visited Vertex // Result of tracing the currently visited Vertex. Use SET_RESULT below! DfgVertex* m_resp = nullptr; DfgVertex* m_defaultp = nullptr; // When tracing a variable, this is its 'defaultp', if any @@ -228,8 +229,7 @@ class TraceDriver final : public DfgVisitor { return varp; } - // Continue tracing drivers of the given vertex, at the given LSB. - // Every visitor should call this to continue the traversal. + // Trace drivers of the given packed vertex, at the given bit range. DfgVertex* trace(DfgVertex* const vtxp, const uint32_t msb, const uint32_t lsb) { UASSERT_OBJ(vtxp->isPacked(), vtxp, "Can only trace packed type vertices"); UASSERT_OBJ(vtxp->size() > msb, vtxp, "Traced Vertex too narrow"); @@ -271,6 +271,7 @@ class TraceDriver final : public DfgVisitor { // Otherwise visit the vertex to trace it VL_RESTORER(m_msb); VL_RESTORER(m_lsb); + VL_RESTORER_CLEAR(m_idxs); VL_RESTORER(m_resp); m_msb = msb; m_lsb = lsb; @@ -285,6 +286,40 @@ class TraceDriver final : public DfgVisitor { return respr; } + // Trace drivers of the given array vertex, at the current m_idxs, m_msb, m_lsb. + DfgVertex* traceSameIdx(DfgVertex* const vtxp) { + UASSERT_OBJ(vtxp->isArray(), vtxp, "Should be an array vertex"); + UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index"); + VL_RESTORER(m_resp); + m_resp = nullptr; + iterate(vtxp); // Resolve the element, the array visitors set 'm_resp' + UASSERT_OBJ(m_resp, vtxp, "Tracing driver failed for " << vtxp->typeName()); + UASSERT_OBJ(m_resp->width() == (m_msb - m_lsb + 1), vtxp, "Wrong result width"); + return m_resp; + } + + // Trace drivers of the given array vertex, pushing the given index, at current m_msb, m_lsb. + DfgVertex* tracePushIdx(DfgVertex* const vtxp, const uint32_t idx) { + const size_t nIdxs = m_idxs.size(); + m_idxs.push_back(idx); + DfgVertex* const resp = traceSameIdx(vtxp); + m_idxs.pop_back(); + UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch"); + return resp; + } + + // Trace drivers of the given vertex, popping the innermost index, at current m_msb, m_lsb. + DfgVertex* tracePopIdx(DfgVertex* const vtxp) { + UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index"); + const size_t nIdxs = m_idxs.size(); + const uint32_t idx = m_idxs.back(); + m_idxs.pop_back(); + DfgVertex* const resp = m_idxs.empty() ? trace(vtxp, m_msb, m_lsb) : traceSameIdx(vtxp); + m_idxs.push_back(idx); + UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch"); + return resp; + } + template Vertex* traceBitwiseBinary(Vertex* vtxp) { static_assert(std::is_base_of::value, @@ -388,25 +423,26 @@ class TraceDriver final : public DfgVisitor { // Gather terms std::vector termps; + uint32_t lsb = m_lsb; for (const Driver& driver : drivers) { // Driver is below the searched LSB, move on - if (m_lsb > driver.m_msb) continue; + if (lsb > driver.m_msb) continue; // Driver is above the searched MSB, done if (driver.m_lsb > m_msb) break; // Gap below this driver, trace default to fill it - if (driver.m_lsb > m_lsb) { + if (driver.m_lsb > lsb) { UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing"); - termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, m_lsb)); - m_lsb = driver.m_lsb; + termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, lsb)); + lsb = driver.m_lsb; } // Driver covers searched range, pick the needed/available bits const uint32_t lim = std::min(m_msb, driver.m_msb); - termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb)); - m_lsb = lim + 1; + termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, lsb - driver.m_lsb)); + lsb = lim + 1; } - if (m_msb >= m_lsb) { + if (m_msb >= lsb) { UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing"); - termps.emplace_back(trace(m_defaultp, m_msb, m_lsb)); + termps.emplace_back(trace(m_defaultp, m_msb, lsb)); } // The earlier cheks cover the case when either a whole driver or the default covers @@ -425,46 +461,58 @@ class TraceDriver final : public DfgVisitor { SET_RESULT(resp); } - void visit(DfgVarPacked* vtxp) override { - UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile VarPacked"); + void visit(DfgSpliceArray* vtxp) override { + // Explicit per-element driver (a UnitArray wrapping the element value) + if (DfgVertex* const driverp = vtxp->driverAt(m_idxs.back())) { + // Consume this index, then trace the element value + SET_RESULT(tracePopIdx(driverp->as()->srcp())); + return; + } + // TODO: this is unreachable, as syntheis can't create it today. + // // Element not driven explicitly, so it comes from the default array. Keep the + // // index pending (the default is the whole array, indexed the same way) and + // // continue tracing it. + // UASSERT_OBJ(m_defaultp, vtxp, "Independent array element should have a driver or + // default"); SET RESULT(traceSameIdx(m_defaultp)); + } + + void visit(DfgVertexVar* vtxp) override { + UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile variable"); VL_RESTORER(m_defaultp); m_defaultp = vtxp->defaultp(); - SET_RESULT(trace(vtxp->srcp(), m_msb, m_lsb)); + DfgVertex* const drvp = vtxp->srcp() ? vtxp->srcp() : m_defaultp; + UASSERT_OBJ(drvp, vtxp, "Should not have to trace undriven variable"); + // Packed variable: trace the driver. Array variable: continue navigating it at + // the pending element (both at the same bit range). + SET_RESULT(m_idxs.empty() ? trace(drvp, m_msb, m_lsb) : traceSameIdx(drvp)); } void visit(DfgArraySel* vtxp) override { - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; - - // If index is not constant, independence was proven only if the 'fromp' is - // independent, so no need to trace that - if (!vtxp->bitp()->is()) { - DfgArraySel* const resp = make(vtxp, vtxp->width()); - resp->fromp(vtxp->fromp()); - resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0)); - DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); - selp->fromp(resp); - selp->lsb(m_lsb); - SET_RESULT(selp); + // If constant index, push it and trace the selected element through the array + // structure of 'fromp'. This handles arbitrarily nested (multi-dimensional) + // arrays, as each nested ArraySel pushes a further index. + if (const DfgConst* const idxp = vtxp->bitp()->cast()) { + SET_RESULT(tracePushIdx(vtxp->fromp(), idxp->toU32())); return; } - // Trace the relevant driver based on the static index - const DfgConst* const idxp = vtxp->bitp()->as(); - UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray"); - // Skip through intermediate variables - while (varp->srcp() && varp->srcp()->is()) { - varp = varp->srcp()->as(); - UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray"); - } - // Find driver - const DfgVertex* srcp = varp->srcp(); - if (const DfgSpliceArray* const splicep = srcp->cast()) { - srcp = splicep->driverAt(idxp->toSizeT()); - } - // Trace the driver, which at this point must be a UnitArray - SET_RESULT(trace(srcp->as()->srcp(), m_msb, m_lsb)); + // If index is not constant, independence was proven only if the 'fromp' is + // independent, so no need to trace that, just reference it with the traced + // index. This only happens at the packed ArraySel leaf. + UASSERT_OBJ(m_idxs.empty(), vtxp, "Non-constant index below an outer array index"); + DfgArraySel* const resp = make(vtxp, vtxp->width()); + resp->fromp(vtxp->fromp()); + resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(selp); + } + + void visit(DfgUnitArray* vtxp) override { + // Single-element array adapter, the pending index must be 0, unwrap the element + UASSERT_OBJ(m_idxs.back() == 0, vtxp, "UnitArray element index should be 0"); + SET_RESULT(tracePopIdx(vtxp->srcp())); } void visit(DfgConcat* vtxp) override { @@ -710,7 +758,7 @@ class TraceDriver final : public DfgVisitor { DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); selp->fromp(resp); selp->lsb(m_lsb); - SET_RESULT(resp); + SET_RESULT(selp); } #undef SET_RESULT @@ -741,6 +789,142 @@ public: } }; +// A bit mask for each bit in a value, which can be either packed or aggregate type +class BitMask final { + // TYPES + const DfgDataType& m_dtype; + union { + V3Number m_num; // The bits, if packed + std::vector m_sub; // The per element masks, if aggregate + }; + +public: + // CONSTRUCTOR + BitMask(FileLine* flp, const DfgDataType& dtype) + : m_dtype{dtype} { + UASSERT_OBJ(!m_dtype.isNull(), flp, "Expected non-null data type"); + if (m_dtype.isPacked()) { + new (&m_num) V3Number{flp, static_cast(m_dtype.size()), 0}; + } else { + new (&m_sub) std::vector{}; + m_sub.reserve(m_dtype.size()); + for (uint32_t i = 0; i < m_dtype.size(); ++i) + m_sub.emplace_back(flp, m_dtype.elemDtype()); + } + } + BitMask(const DfgVertex& vtx) + : BitMask{vtx.fileline(), vtx.dtype()} {} + BitMask(const BitMask& other) + : m_dtype{other.m_dtype} { + if (m_dtype.isPacked()) { + new (&m_num) V3Number{other.m_num}; + } else { + new (&m_sub) std::vector{other.m_sub}; + } + } + BitMask& operator=(const BitMask& other) { + if (this != &other) { + UASSERT(m_dtype == other.m_dtype, "Expected same data type"); + if (other.m_dtype.isPacked()) { + m_num = other.m_num; + } else { + m_sub = other.m_sub; + } + } + return *this; + } + ~BitMask() { + if (m_dtype.isPacked()) { + m_num.~V3Number(); + } else { + m_sub.~vector(); + } + } + + // METHODS + V3Number& num() { + UASSERT(m_dtype.isPacked(), "Expected packed data type"); + return m_num; + } + const V3Number& num() const { + UASSERT(m_dtype.isPacked(), "Expected packed data type"); + return m_num; + } + std::vector& sub() { + UASSERT(m_dtype.isArray(), "Expected array data type"); + return m_sub; + } + + bool isZero() const { + if (m_dtype.isPacked()) return m_num.isEqZero(); + + for (const BitMask& sub : m_sub) { + if (!sub.isZero()) return false; + } + return true; + } + bool isOnes() const { + if (m_dtype.isPacked()) return m_num.isEqAllOnes(); + + for (const BitMask& sub : m_sub) { + if (!sub.isOnes()) return false; + } + return true; + } + void setZero() { + if (m_dtype.isPacked()) { + m_num.setAllBits0(); + } else { + for (BitMask& sub : m_sub) sub.setZero(); + } + } + void setOnes() { + if (m_dtype.isPacked()) { + m_num.setAllBits1(); + } else { + for (BitMask& sub : m_sub) sub.setOnes(); + } + } + + bool operator==(const BitMask& other) const { + UASSERT(m_dtype == other.m_dtype, "Expected same data type"); + if (m_dtype.isPacked()) return m_num.isCaseEq(other.m_num); + return m_sub == other.m_sub; + } + bool operator!=(const BitMask& other) const { return !(*this == other); } + + // 'this' has a clear bit where 'that' has a set bit, that is, + // 'this' has a proper subset of bits set compared to 'that'. + bool isContractionOf(const BitMask& that) const { + UASSERT(m_dtype == that.m_dtype, "Expected same data type"); + if (m_dtype.isPacked()) { + const size_t words = VL_WORDS_I(m_dtype.size()); + for (size_t i = 0; i < words; ++i) { + if (~m_num.edataWord(i) & that.m_num.edataWord(i)) return true; + } + return false; + } + + UASSERT(m_dtype.isArray(), "Expected array data type"); + for (size_t i = 0; i < m_sub.size(); ++i) { + if (m_sub[i].isContractionOf(that.m_sub[i])) return true; + } + return false; + } + + std::string toString() const { + if (m_dtype.isPacked()) return "0x" + m_num.displayed(m_num.fileline(), "%x"); + std::string result; + result += "{"; + for (const BitMask& sub : m_sub) { + if (&sub != &m_sub.front()) result += ", "; + result += sub.toString(); + } + result += "}"; + return result; + } +}; + class IndependentBits final : public DfgVisitor { // TYPES struct VertexState final { @@ -751,7 +935,7 @@ class IndependentBits final : public DfgVisitor { // STATE const SccInfo& m_sccInfo; // The SccInfo instance // Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC - std::unordered_map m_vtxp2Mask; + std::unordered_map m_vtxp2Mask; // Work list for the traversal (min-queue of vertex RPO numbers) std::priority_queue, std::greater> m_workQueue; std::unordered_map m_vtxp2State; // State of each vertex @@ -761,21 +945,13 @@ class IndependentBits final : public DfgVisitor { #endif // METHODS - // Predicate to check if a vertex should be analysed directly - bool handledDirectly(const DfgVertex& vtx) const { - if (!vtx.isPacked()) return false; - if (vtx.is()) return false; - return true; - } // Retrieve the mask for the given vertex (create it with value 0 if needed) - V3Number& mask(const DfgVertex& vtx) { - UASSERT_OBJ(handledDirectly(vtx), &vtx, "Vertex should not be handled direclty"); - // Look up (or create) mask for 'vtxp' + BitMask& mask(const DfgVertex& vtx) { + // Look up (or create) mask for 'vtx' return m_vtxp2Mask .emplace(std::piecewise_construct, // - std::forward_as_tuple(&vtx), // - std::forward_as_tuple(vtx.fileline(), static_cast(vtx.width()), 0)) // + std::forward_as_tuple(&vtx), std::forward_as_tuple(vtx)) .first->second; } @@ -784,7 +960,7 @@ class IndependentBits final : public DfgVisitor { // TODO: Use C++20 std::source_location instead of a macro #ifdef VL_DEBUG #define MASK(vtxp) \ - ([this](const DfgVertex& vtx) -> V3Number& { \ + ([this](const DfgVertex& vtx) -> BitMask& { \ if (VL_UNLIKELY(m_lineCoverageFile.is_open())) m_lineCoverageFile << __LINE__ << '\n'; \ return mask(vtx); \ }(*vtxp)) @@ -810,13 +986,24 @@ class IndependentBits final : public DfgVisitor { } } - void propagateFromDriver(V3Number& m, const DfgVertex* srcp) { + void propagateFromDriver(BitMask& m, const DfgVertex* srcp) { // If there is no driver, we are done if (!srcp) return; // If it is driven by a splice, we need to combine the masks of the drivers if (const DfgSplicePacked* const splicep = srcp->cast()) { splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) { - m.opSelInto(MASK(&src), lo, src.width()); + m.num().opSelInto(MASK(&src).num(), lo, src.width()); + return false; + }); + return; + } + if (const DfgSpliceArray* const splicep = srcp->cast()) { + splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) { + if (const DfgUnitArray* const uap = src.cast()) { + m.sub().at(lo) = MASK(uap->srcp()); + } else { + // m.sub().at(lo) = Can't happen MASK(&src); + } return false; }); return; @@ -827,15 +1014,15 @@ class IndependentBits final : public DfgVisitor { // VISITORS void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START - UASSERT_OBJ(handledDirectly(*vtxp), vtxp, "Vertex should be handled direclty"); UINFO(9, "IndependentBits - Unhandled vertex type: " << vtxp->typeName()); // Conservative assumption about all bits being dependent prevails } // LCOV_EXCL_STOP - void visit(DfgVarPacked* vtxp) override { + void visit(DfgVertexVar* vtxp) override { // We cannot trace through a volatile variable, so pretend all bits are dependent if (vtxp->isVolatile()) return; - V3Number& m = MASK(vtxp); + + BitMask& m = MASK(vtxp); DfgVertex* const srcp = vtxp->srcp(); DfgVertex* const defaultp = vtxp->defaultp(); // If there is a default driver, we start from that @@ -844,81 +1031,62 @@ class IndependentBits final : public DfgVisitor { propagateFromDriver(m, srcp); } - void visit(DfgArraySel* vtxp) override { - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; + void visit(DfgVertexSplice* vtxp) override { + propagateFromDriver(MASK(vtxp), vtxp); // Needed to continue traversal + } - // If index is not constant, independent only if the variable index - // is indenpendent and the array is independent. We don't track arrays, - // so we will assume an array is only independent if it has no drivers - // in the graph. TODO: could check all drivers. - if (!vtxp->bitp()->is()) { - if (MASK(vtxp->bitp()).isEqAllOnes() && !varp->srcp() && !varp->defaultp()) { - MASK(vtxp).setAllBits1(); - } + void visit(DfgUnitArray* vtxp) override { MASK(vtxp).sub().at(0) = MASK(vtxp->srcp()); } + + void visit(DfgArraySel* vtxp) override { + DfgVertex* const fromp = vtxp->fromp(); + + // If constant index, copy mask of relevant element + if (const DfgConst* const idxp = vtxp->bitp()->cast()) { + MASK(vtxp) = MASK(vtxp->fromp()).sub().at(idxp->toSizeT()); return; } - // Trace the relevant driver based on the static index - const DfgConst* const idxp = vtxp->bitp()->as(); - // We cannot trace through a volatile variable, so pretend all bits are dependent - if (varp->isVolatile()) return; - // Skip through intermediate variables - while (varp->srcp() && varp->srcp()->is()) { - varp = varp->srcp()->as(); - if (varp->isVolatile()) return; - } - // Find driver - const DfgVertex* srcp = varp->srcp(); - if (!srcp) return; - if (const DfgSpliceArray* const splicep = srcp->cast()) { - srcp = splicep->driverAt(idxp->toSizeT()); - if (!srcp) return; - } - const DfgUnitArray* uap = srcp->cast(); - if (!uap) return; - srcp = uap->srcp(); - // Propagate from driver - propagateFromDriver(MASK(vtxp), srcp); + // If index is not constant, independent only if the index is indenpendent, and the array + // is independent. TODO: could relax by '&' reducing, not sure if worth it. + if (MASK(vtxp->bitp()).isOnes() && MASK(fromp).isOnes()) MASK(vtxp).setOnes(); } void visit(DfgConcat* vtxp) override { const DfgVertex* const rhsp = vtxp->rhsp(); const DfgVertex* const lhsp = vtxp->lhsp(); - V3Number& m = MASK(vtxp); - m.opSelInto(MASK(rhsp), 0, rhsp->width()); - m.opSelInto(MASK(lhsp), rhsp->width(), lhsp->width()); + V3Number& m = MASK(vtxp).num(); + m.opSelInto(MASK(rhsp).num(), 0, rhsp->width()); + m.opSelInto(MASK(lhsp).num(), rhsp->width(), lhsp->width()); } void visit(DfgRep* vtxp) override { const uint32_t count = vtxp->count(); const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& vMask = MASK(vtxp); - V3Number& sMask = MASK(srcp); + V3Number& vMask = MASK(vtxp).num(); + V3Number& sMask = MASK(srcp).num(); for (uint32_t i = 0; i < count; ++i) vMask.opSelInto(sMask, i * sWidth, sWidth); } void visit(DfgSel* vtxp) override { const uint32_t lsb = vtxp->lsb(); const uint32_t msb = lsb + vtxp->width() - 1; - MASK(vtxp).opSel(MASK(vtxp->fromp()), msb, lsb); + MASK(vtxp).num().opSel(MASK(vtxp->fromp()).num(), msb, lsb); } void visit(DfgExtend* vtxp) override { const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& s = MASK(srcp); - V3Number& m = MASK(vtxp); + V3Number& s = MASK(srcp).num(); + V3Number& m = MASK(vtxp).num(); m.opSelInto(s, 0, sWidth); m.opSetRange(sWidth, vtxp->width() - sWidth, '1'); } void visit(DfgExtendS* vtxp) override { const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& s = MASK(srcp); - V3Number& m = MASK(vtxp); + V3Number& s = MASK(srcp).num(); + V3Number& m = MASK(vtxp).num(); m.opSelInto(s, 0, sWidth); m.opSetRange(sWidth, vtxp->width() - sWidth, s.bitIs0(sWidth - 1) ? '0' : '1'); } @@ -928,71 +1096,62 @@ class IndependentBits final : public DfgVisitor { } void visit(DfgAnd* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgOr* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgXor* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgAdd* vtxp) override { - V3Number& m = MASK(vtxp); - m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + V3Number& m = MASK(vtxp).num(); + m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); floodTowardsMsb(m); } void visit(DfgSub* vtxp) override { // Same as Add: 2's complement (a - b) == (a + ~b + 1) - V3Number& m = MASK(vtxp); - m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + V3Number& m = MASK(vtxp).num(); + m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); floodTowardsMsb(m); } - void visit(DfgRedAnd* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedAnd* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } - void visit(DfgRedOr* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedOr* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } - void visit(DfgRedXor* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedXor* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgEq* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgNeq* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgLt* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgLte* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgGt* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgGte* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgShiftRS* vtxp) override { @@ -1004,14 +1163,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - if (MASK(lhsp).bitIs0(width - 1)) { - MASK(vtxp).setAllBits0(); - } else { - MASK(vtxp).setAllBits1(); - } + if (MASK(lhsp).num().bitIs1(width - 1)) { MASK(vtxp).setOnes(); } } else { - V3Number& m = MASK(vtxp); - m.opShiftRS(MASK(lhsp), rConstp->num(), width); + V3Number& m = MASK(vtxp).num(); + m.opShiftRS(MASK(lhsp).num(), rConstp->num(), width); m.opSetRange(width - shiftAmount, shiftAmount, '1'); } return; @@ -1020,9 +1175,9 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive top bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsLsb(m); } } @@ -1036,10 +1191,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - MASK(vtxp).setAllBits1(); + MASK(vtxp).setOnes(); } else { - V3Number& m = MASK(vtxp); - m.opShiftR(MASK(lhsp), rConstp->num()); + V3Number& m = MASK(vtxp).num(); + m.opShiftR(MASK(lhsp).num(), rConstp->num()); m.opSetRange(width - shiftAmount, shiftAmount, '1'); } return; @@ -1048,9 +1203,9 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive top bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsLsb(m); } } @@ -1064,10 +1219,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - MASK(vtxp).setAllBits1(); + MASK(vtxp).setOnes(); } else { - V3Number& m = MASK(vtxp); - m.opShiftL(MASK(lhsp), rConstp->num()); + V3Number& m = MASK(vtxp).num(); + m.opShiftL(MASK(lhsp).num(), rConstp->num()); m.opSetRange(0, shiftAmount, '1'); } return; @@ -1076,23 +1231,21 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive bottom bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsMsb(m); } } void visit(DfgCond* vtxp) override { - if (MASK(vtxp->condp()).isEqAllOnes()) { - MASK(vtxp).opAnd(MASK(vtxp->thenp()), MASK(vtxp->elsep())); + if (MASK(vtxp->condp()).isOnes()) { + MASK(vtxp).num().opAnd(MASK(vtxp->thenp()).num(), MASK(vtxp->elsep()).num()); } } void visit(DfgMatchMasked* vtxp) override { - if (MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + if (MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->matchp()).isOnes()) { MASK(vtxp).setOnes(); } } #undef MASK @@ -1102,11 +1255,6 @@ class IndependentBits final : public DfgVisitor { vtx.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of an SCC, already has all bits marked independent if (!m_sccInfo.get(sink)) return false; - // If a vertex is not handled directly, recursively enqueue its sinks instead - if (!handledDirectly(sink)) { - enqueueSinks(sink); - return false; - } // Otherwise just enqueue it VertexState& state = m_vtxp2State.at(&sink); if (!state.m_isOnWorkList) { @@ -1169,18 +1317,15 @@ class IndependentBits final : public DfgVisitor { // always assign constants bits, which are always independent (eg Extend/Shift) // Enqueue sinks of all SCC vertices that have at least one independent bit for (DfgVertex* const vtxp : rpoEnumeration) { - if (!handledDirectly(*vtxp)) continue; if (m_sccInfo.get(*vtxp)) continue; - mask(*vtxp).setAllBits1(); + mask(*vtxp).setOnes(); } for (DfgVertex* const vtxp : rpoEnumeration) { - if (!handledDirectly(*vtxp)) continue; if (!m_sccInfo.get(*vtxp)) continue; iterate(vtxp); - UINFO(9, "Initial independent bits of " - << vtxp << " " << vtxp->typeName() - << " are: " << mask(*vtxp).displayed(vtxp->fileline(), "%b")); - if (!mask(*vtxp).isEqZero()) enqueueSinks(*vtxp); + UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName() + << " are: " << mask(*vtxp).toString()); + if (!mask(*vtxp).isZero()) enqueueSinks(*vtxp); } // Propagate independent bits until no more changes are made @@ -1191,13 +1336,11 @@ class IndependentBits final : public DfgVisitor { m_vtxp2State.at(currp).m_isOnWorkList = false; // Should not enqueue vertices that are not in an SCC UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC"); - // Should only enqueue packed vertices - UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly"); // Grab current mask of item - const V3Number& currMask = mask(*currp); + const BitMask& currMask = mask(*currp); // Remember current mask so we can check if it changed - const V3Number prevMask = currMask; + const BitMask prevMask = currMask; UINFO(9, "Analyzing independent bits of " << currp << " " << currp->typeName()); @@ -1205,19 +1348,16 @@ class IndependentBits final : public DfgVisitor { iterate(currp); // If mask changed, enqueue sinks - if (!prevMask.isCaseEq(currMask)) { + if (prevMask != currMask) { UINFO(9, "Independent bits of " // << currp << " " << currp->typeName() << " changed" // - << "\n form: " << prevMask.displayed(currp->fileline(), "%b") - << "\n to: " << currMask.displayed(currp->fileline(), "%b")); + << "\n form: " << prevMask.toString() + << "\n to: " << currMask.toString()); enqueueSinks(*currp); // Check the mask only ever expands (no bit goes 1 -> 0) if (VL_UNLIKELY(v3Global.opt.debugCheck())) { - V3Number notCurr{currMask}; - notCurr.opNot(currMask); - V3Number prevAndNotCurr{currMask}; - prevAndNotCurr.opAnd(prevMask, notCurr); - UASSERT_OBJ(prevAndNotCurr.isEqZero(), currp, "Mask should only expand"); + UASSERT_OBJ(!currMask.isContractionOf(prevMask), currp, + "Mask should only expand"); } } } @@ -1228,8 +1368,8 @@ public: // returns a map from vertices to a bit mask, where a bit in the mask is // set if the corresponding bit in that vertex is known to be independent // of the values of vertices in the same SCC as the vertex resides in. - static std::unordered_map apply(DfgGraph& dfg, - const SccInfo& sccInfo) { + static std::unordered_map apply(DfgGraph& dfg, + const SccInfo& sccInfo) { return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask); } }; @@ -1239,21 +1379,21 @@ class FixUp final { SccInfo& m_sccInfo; // The SccInfo instance TraceDriver m_traceDriver{m_dfg, m_sccInfo}; // The independent bits map - const std::unordered_map m_independentBits + const std::unordered_map m_independentBits = IndependentBits::apply(m_dfg, m_sccInfo); - size_t m_nImprovements = 0; // Number of improvements mde + size_t m_nImprovements = 0; // Number of improvements made // Returns a bitmask set if that bit of 'vtx' is used (has a sink) - static V3Number computeUsedBits(DfgVertex& vtx) { - V3Number result{vtx.fileline(), static_cast(vtx.width()), 0}; + static BitMask computeUsedBits(DfgVertex& vtx) { + BitMask result{vtx}; vtx.foreachSink([&result](DfgVertex& sink) { // If used via a Sel, mark the selected bits used if (const DfgSel* const selp = sink.cast()) { - result.opSetRange(selp->lsb(), selp->width(), '1'); + result.num().opSetRange(selp->lsb(), selp->width(), '1'); return false; } // Used without a Sel, so all bits are used - result.setAllBits1(); + result.setOnes(); return true; }); return result; @@ -1306,20 +1446,18 @@ class FixUp final { UASSERT_OBJ(!vtx.is(), &vtx, "Should not be a splice"); // Get which bits of 'vtxp' are independent of the SCCs - const V3Number& indpBits = m_independentBits.at(&vtx); - UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " - << indpBits.displayed(vtx.fileline(), "%b")); + const BitMask& indpBits = m_independentBits.at(&vtx); + UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " << indpBits.toString()); // Can't do anything if all bits are dependent - if (indpBits.isEqZero()) return; + if (indpBits.isZero()) return; // Figure out which bits of 'vtxp' are used - const V3Number usedBits = computeUsedBits(vtx); - UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " - << usedBits.displayed(vtx.fileline(), "%b")); + const BitMask usedBits = computeUsedBits(vtx); + UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " << usedBits.toString()); // Nothing to do if no used bits are independen (all used bits are dependent) V3Number usedAndIndependent{vtx.fileline(), static_cast(vtx.width()), 0}; - usedAndIndependent.opAnd(usedBits, indpBits); + usedAndIndependent.opAnd(usedBits.num(), indpBits.num()); if (usedAndIndependent.isEqZero()) return; // We are computing the terms to concatenate and replace 'vtxp' with @@ -1328,14 +1466,14 @@ class FixUp final { // Iterate through consecutive used/unused ranges FileLine* const flp = vtx.fileline(); const uint32_t width = vtx.width(); - bool isUsed = usedBits.bitIs1(0); // Is current range used + bool isUsed = usedBits.num().bitIs1(0); // Is current range used uint32_t lsb = 0; // LSB of current range for (uint32_t msb = 0; msb < width; ++msb) { - const bool endRange = msb == width - 1 || isUsed != usedBits.bitIs1(msb + 1); + const bool endRange = msb == width - 1 || isUsed != usedBits.num().bitIs1(msb + 1); if (!endRange) continue; if (isUsed) { // The range is used, compute the replacement terms - gatherTerms(termps, vtx, indpBits, msb, lsb); + gatherTerms(termps, vtx, indpBits.num(), msb, lsb); } else { // The range is not used, just use constant 0 as a placeholder DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0}; diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index ec6c6b339..217863f38 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -92,6 +92,7 @@ test.compile(verilator_flags2=[ "--prefix", "Vopt", "-Werror-UNOPTFLAT", "--dumpi-V3DfgBreakCycles", "9", # To fill code coverage + "--debugi-V3DfgBreakCycles", "9", # To fill code coverage "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index b2124b1d6..6114336fe 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -361,6 +361,19 @@ module t ( assign ALWAYS_2 = always_2; // verilator lint_on ALWCOMBORDER + // verilator lint_off ALWCOMBORDER + logic [4:0] always_3; + always_comb begin + always_3[4] = always_3[0]; + always_3[0] = rand_a[0]; + always_3[2] = rand_a[2]; + always_3[3] = |always_3[2:1]; + end + assign always_3[1] = always_3[0]; + `signal(ALWAYS_3, 5); // UNOPTFLAT + assign ALWAYS_3 = always_3; + // verilator lint_on ALWCOMBORDER + logic [31:0] array_4[3]; // UNOPTFLAT // Input assign array_4[0] = rand_a[31:0]; From 198cce29ba7c6f9c615a163d5a2089257fce4c12 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 14:26:30 +0100 Subject: [PATCH 07/24] CI: Restore relocation testing (#7870) PR #7776 removed Ubuntu 22.04 clang from the test matrix, which was the only one testing the relocation of an installation. Add back under Ubuntu 24.04 clang. --- .github/workflows/build-test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 7817a3bf6..e6737f17e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -263,14 +263,14 @@ jobs: fail-fast: false matrix: include: - # Ubuntu 24.04 clang - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-2} + # Ubuntu 24.04 clang, also test relocation + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-0} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-1} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-2} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-3} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-0} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-1} + - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-2} test-2204-gcc: name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} From 6e06cd18daffab57212c1b49623bec257a22d226 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 15:46:30 +0100 Subject: [PATCH 08/24] CI: Decouple build and test into separate scripts, simplify workflows (#7871) - Split the monolithic ci-script.bash into ci-build.bash and ci-test.bash, with shared setup in ci-common.bash. - Pass job parameters as explicit command-line options rather than environment variables (--compiler, --prefix, --suite, --asan, --gcov, --reloc, --ccwarn). - Detect the host OS and distribution at runtime (uname, /etc/os-release) instead of the legacy Travis-era CI_OS_NAME, CI_RUNS_ON and CI_BUILD_STAGE_NAME variables. - Remove dead code: FreeBSD support, Windows wavediff install, the unused CI_COMMIT variable, and various redundant operations. - Inline the single-entry build-job matrices. - Rename osx to macOS - Refuse to run these ci scripts outside GitHub Actions to protect developer checkouts. --- .github/workflows/build-test.yml | 122 +++++---------- .github/workflows/coverage.yml | 1 - .github/workflows/format.yml | 6 - .github/workflows/reusable-build.yml | 21 +-- .github/workflows/reusable-lint-py.yml | 5 +- .github/workflows/reusable-test.yml | 14 +- ci/ci-build.bash | 82 ++++++++++ ci/ci-common.bash | 37 +++++ ci/ci-install.bash | 173 ++++++++++---------- ci/ci-script.bash | 208 ------------------------- ci/ci-test.bash | 166 ++++++++++++++++++++ 11 files changed, 426 insertions(+), 409 deletions(-) create mode 100755 ci/ci-build.bash create mode 100644 ci/ci-common.bash delete mode 100755 ci/ci-script.bash create mode 100755 ci/ci-test.bash diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e6737f17e..e618873cd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -29,127 +29,79 @@ concurrency: jobs: build-2604-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | ubuntu-26.04 | gcc uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: ubuntu-26.04 + cc: gcc + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-26.04, cc: gcc, asan: 0} build-2604-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | ubuntu-26.04 | clang uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: ubuntu-26.04 + cc: clang + dev-asan: 1 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-26.04, cc: clang, asan: 1} build-2404-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | ubuntu-24.04 | gcc uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: ubuntu-24.04 + cc: gcc + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-24.04, cc: gcc, asan: 0} build-2404-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | ubuntu-24.04 | clang uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: ubuntu-24.04 + cc: clang + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-24.04, cc: clang, asan: 0} build-2204-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | ubuntu-22.04 | gcc uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: ubuntu-22.04 + cc: gcc + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-osx-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + build-macos-15-gcc: + name: Build | macos-15 | gcc uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: osx - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: macos-15 + cc: gcc + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: macos-15, cc: gcc, asan: 0} - build-osx-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + build-macos-15-clang: + name: Build | macos-15 | clang uses: ./.github/workflows/reusable-build.yml with: sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: osx - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} + os: macos-15 + cc: clang + dev-asan: 0 dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: macos-15, cc: clang, asan: 0} build-windows: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - {os: windows-2025-vs2026, cc: msvc} + name: Build | windows-2025-vs2026 | msvc + runs-on: windows-2025-vs2026 env: - CI_OS_NAME: win CCACHE_COMPRESS: 1 CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 @@ -177,7 +129,7 @@ jobs: name: verilator-win.zip test-2604-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} needs: build-2604-gcc uses: ./.github/workflows/reusable-test.yml with: @@ -201,7 +153,7 @@ jobs: - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2} test-2604-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} needs: build-2604-clang uses: ./.github/workflows/reusable-test.yml with: @@ -225,7 +177,7 @@ jobs: - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2} test-2404-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} needs: build-2404-gcc uses: ./.github/workflows/reusable-test.yml with: @@ -249,7 +201,7 @@ jobs: - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-2} test-2404-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} needs: build-2404-clang uses: ./.github/workflows/reusable-test.yml with: @@ -273,7 +225,7 @@ jobs: - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-2} test-2204-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} needs: build-2204-gcc uses: ./.github/workflows/reusable-test.yml with: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 38d4c9a50..3b21b8f48 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -43,7 +43,6 @@ jobs: # between the root of the pull request and the target branch sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} os: ubuntu-24.04 - os-name: linux cc: gcc dev-asan: 0 dev-gcov: 1 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 5a604fa73..693190453 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -15,18 +15,12 @@ jobs: format: runs-on: ubuntu-24.04 name: Ubuntu 24.04 | format - env: - CI_OS_NAME: linux - CI_RUNS_ON: ubuntu-24.04 - CI_COMMIT: ${{ github.sha }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build - env: - CI_BUILD_STAGE_NAME: build run: | sudo apt install clang-format-18 || \ sudo apt install clang-format-18 diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index f19fa4ad1..a956d927e 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -17,9 +17,6 @@ on: cc: # 'clang' or 'gcc' required: true type: string - os-name: # 'linux' or 'osx' - required: true - type: string dev-asan: required: true type: number @@ -32,12 +29,9 @@ on: value: ${{ jobs.build.outputs.archive }} env: - CI_OS_NAME: ${{ inputs.os-name }} CCACHE_COMPRESS: 1 CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 - INSTALL_DIR: ${{ github.workspace }}/install - RELOC_DIR: ${{ github.workspace }}/relloc defaults: run: @@ -52,11 +46,6 @@ jobs: outputs: archive: ${{ steps.create-archive.outputs.archive }} env: - CI_BUILD_STAGE_NAME: build - CI_DEV_ASAN: ${{ inputs.dev-asan }} - CI_DEV_GCOV: ${{ inputs.dev-gcov }} - CI_RUNS_ON: ${{ inputs.os }} - CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} CACHE_BASE_KEY: build-${{ inputs.os }}-${{ inputs.cc }} CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) steps: @@ -78,10 +67,16 @@ jobs: ${{ env.CACHE_KEY }}- - name: Install packages for build - run: ./ci/ci-install.bash + run: ./ci/ci-install.bash build - name: Build - run: ./ci/ci-script.bash + run: | + ./ci/ci-build.bash \ + --prefix ${{ github.workspace }}/install \ + --compiler ${{ inputs.cc }} \ + --ccwarn \ + ${{ inputs.dev-asan && '--asan' || '' }} \ + ${{ inputs.dev-gcov && '--gcov' || '' }} - name: Create repository archive id: create-archive diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index f37027aa7..ae7ff30b6 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -8,9 +8,6 @@ on: workflow_call: env: - CI_OS_NAME: linux - CI_BUILD_STAGE_NAME: build - CI_RUNS_ON: ubuntu-22.04 CCACHE_COMPRESS: 1 CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 @@ -32,7 +29,7 @@ jobs: path: repo - name: Install packages for build - run: ./ci/ci-install.bash + run: ./ci/ci-install.bash build - name: Configure run: | diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index b36134108..1c393c4f0 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -28,12 +28,9 @@ on: type: number env: - CI_OS_NAME: linux CCACHE_COMPRESS: 1 CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 - INSTALL_DIR: ${{ github.workspace }}/install - RELOC_DIR: ${{ github.workspace }}/relloc defaults: run: @@ -46,9 +43,6 @@ jobs: runs-on: ${{ inputs.os }} name: Test env: - CI_BUILD_STAGE_NAME: test - CI_RUNS_ON: ${{ inputs.os }} - CI_RELOC: ${{inputs.reloc }} CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} CACHE_BASE_KEY: test-${{ inputs.os }}-${{ inputs.cc }}-${{inputs.reloc }}-${{ inputs.suite }} CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total) @@ -78,17 +72,17 @@ jobs: - name: Install test dependencies run: | - ./ci/ci-install.bash + ./ci/ci-install.bash test make venv - name: Test id: run-test continue-on-error: true - env: - TESTS: ${{ inputs.suite }} run: | source .venv/bin/activate - ./ci/ci-script.bash + ./ci/ci-test.bash \ + --suite ${{ inputs.suite }} \ + ${{ inputs.reloc && format('--reloc {0}/reloc', github.workspace) || '' }} - name: Combine code coverage data if: ${{ inputs.dev-gcov }} diff --git a/ci/ci-build.bash b/ci/ci-build.bash new file mode 100755 index 000000000..722ed6563 --- /dev/null +++ b/ci/ci-build.bash @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI build job script +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Executed in the 'build' stage. +################################################################################ + +# Destructive to the checkout (reconfigures and rebuilds); never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + +set -e +set -x + +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +OPT_ASAN=0 +OPT_CCWARN=0 +OPT_GCOV=0 +OPT_COMPILER= +OPT_PREFIX= +while [ $# -gt 0 ]; do + case "$1" in + --asan) OPT_ASAN=1 ;; + --ccwarn) OPT_CCWARN=1 ;; + --compiler) + [ $# -ge 2 ] || fatal "--compiler requires an argument" + OPT_COMPILER="$2" + shift + ;; + --gcov) OPT_GCOV=1 ;; + --prefix) + [ $# -ge 2 ] || fatal "--prefix requires an argument" + OPT_PREFIX="$2" + shift + ;; + *) fatal "Unknown option: '$1'" ;; + esac + shift +done + +[ -n "$OPT_COMPILER" ] || fatal "--compiler is required" +[ -n "$OPT_PREFIX" ] || fatal "--prefix is required" + +# Map the compiler name to the executable name configure CXX expects +case "$OPT_COMPILER" in + clang) CXX=clang++ ;; + gcc) CXX=g++ ;; + *) fatal "Unknown compiler: '$OPT_COMPILER'" ;; +esac + +################################################################################ +# Configure + +CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests" +if [ "$OPT_CCWARN" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn" +fi +if [ "$OPT_ASAN" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan" + CXX="$CXX -DVL_LEAK_CHECKS" +fi +if [ "$OPT_GCOV" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov" +fi +autoconf +./configure $CONFIGURE_ARGS CXX="$CXX" + +################################################################################ +# Build + +ccache -z +"$MAKE" -j "$NPROC" -k +ccache -svv diff --git a/ci/ci-common.bash b/ci/ci-common.bash new file mode 100644 index 000000000..ef2fe23c8 --- /dev/null +++ b/ci/ci-common.bash @@ -0,0 +1,37 @@ +# DESCRIPTION: Verilator: CI common definitions, sourced by the stage scripts +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Common definitions sourced by the CI stage scripts; not executed directly. +################################################################################ + +fatal() { + echo "ERROR: $(basename "$0"): $1" >&2; exit 1; +} + +case "$(uname -s)" in + Linux) + HOST_OS=linux + MAKE=make + NPROC=$(nproc) + # Distro id/version; subshell keeps os-release out of our namespace + if [ -r /etc/os-release ]; then + DISTRO_ID=$(. /etc/os-release; echo "$ID") + DISTRO_VERSION=$(. /etc/os-release; echo "$VERSION_ID") + fi + ;; + Darwin) + HOST_OS=macOS + MAKE=make + NPROC=$(sysctl -n hw.logicalcpu) + # Disable ccache, doesn't always work in GitHub Actions + export OBJCACHE= + ;; + *) + fatal "Unknown host OS: '$(uname -s)'" + ;; +esac + +export MAKE diff --git a/ci/ci-install.bash b/ci/ci-install.bash index b3ae2e06d..db71c8159 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -1,7 +1,7 @@ #!/usr/bin/env bash # DESCRIPTION: Verilator: CI dependency install script # -# SPDX-FileCopyrightText: 2020 Geza Lore +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ################################################################################ @@ -10,30 +10,33 @@ # required by the particular build stage. ################################################################################ +# Installs system packages with sudo; never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + set -e set -x cd $(dirname "$0")/.. +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +# Which stage to install dependencies for: 'build' or 'test' +STAGE="$1" + +################################################################################ +# Install dependencies + # Avoid occasional cpan failures "Issued certificate has expired." export PERL_LWP_SSL_VERIFY_HOSTNAME=0 echo "check_certificate = off" >> ~/.wgetrc -fatal() { - echo "ERROR: $(basename "$0"): $1" >&2; exit 1; -} - -if [ "$CI_OS_NAME" = "linux" ]; then - MAKE=make -elif [ "$CI_OS_NAME" = "osx" ]; then - MAKE=make -elif [ "$CI_OS_NAME" = "freebsd" ]; then - MAKE=gmake -else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" -fi - -if [ "$CI_OS_NAME" = "linux" ]; then +if [ "$HOST_OS" = "linux" ]; then # Avoid slow "processing triggers for man db" echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc @@ -44,100 +47,100 @@ install-wavediff() { source ci/docker/buildenv/wavetools.conf local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}" local _platform - if [ "$CI_OS_NAME" = "linux" ]; then + if [ "$HOST_OS" = "linux" ]; then _platform="linux-x86_64" - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then _platform="macos-arm64" - elif [ "$CI_OS_NAME" = "windows" ]; then - _platform="windows-x86_64" else - echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping" + echo "WARNING: No wavetools binary available for HOST_OS=$HOST_OS, skipping" return 0 fi local _tmpdir _tmpdir=$(mktemp -d) local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}" - if [ "$CI_OS_NAME" = "windows" ]; then - wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip" - unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}" - else - wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz" - tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}" - fi + wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz" + tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}" sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff rm -rf "${_tmpdir}" } -if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then +if [ "$STAGE" = "build" ]; then ############################################################################## # Dependencies of jobs in the 'build' stage, i.e.: packages required to # build Verilator - if [ "$CI_OS_NAME" = "linux" ]; then - sudo apt-get update || - sudo apt-get update - sudo apt-get install --yes ccache help2man libfl-dev || - sudo apt-get install --yes ccache help2man libfl-dev - if [[ ! "$CI_RUNS_ON" =~ "ubuntu-22.04" ]]; then - # Some conflict of libunwind verison on 22.04, can live without it for now - sudo apt-get install --yes libjemalloc-dev || - sudo apt-get install --yes libjemalloc-dev - fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then - sudo apt-get install --yes libsystemc libsystemc-dev || - sudo apt-get install --yes libsystemc libsystemc-dev + if [ "$HOST_OS" = "linux" ]; then + if [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + bear + ccache + help2man + libfl-dev + libsystemc-dev + mold + ) + # libunwind conflict on 22.04, can live without libjemalloc there + if [ "$DISTRO_VERSION" != "22.04" ]; then + PACKAGES+=(libjemalloc-dev) fi + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - sudo apt-get install --yes bear mold || - sudo apt-get install --yes bear mold - fi - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then + PACKAGES=( + autoconf + bison + ccache + flex + gperftools + help2man + perl + ) brew update || brew update - brew install ccache perl gperftools autoconf bison flex help2man || - brew install ccache perl gperftools autoconf bison flex help2man - elif [ "$CI_OS_NAME" = "freebsd" ]; then - sudo pkg install -y autoconf bison ccache gmake perl5 + brew install "${PACKAGES[@]}" || + brew install "${PACKAGES[@]}" else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" + fatal "Unknown HOST_OS: '$HOST_OS'" fi - - if [ -n "$CCACHE_DIR" ]; then - mkdir -p "$CCACHE_DIR" - fi -elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then +elif [ "$STAGE" = "test" ]; then ############################################################################## # Dependencies of jobs in the 'test' stage, i.e.: packages required to # run the tests - if [ "$CI_OS_NAME" = "linux" ]; then - sudo apt-get update || - sudo apt-get update - # libfl-dev needed for internal coverage's test runs - sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 || - sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 - # Required for test_regress/t/t_dist_attributes.py - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - sudo apt-get install --yes python3-clang mold || - sudo apt-get install --yes python3-clang mold + if [ "$HOST_OS" = "linux" ]; then + if [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + ccache + gdb + jq + lcov + libfl-dev + libsystemc-dev + mold + python3-clang + z3 + ) + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then - sudo apt-get install --yes libsystemc libsystemc-dev || - sudo apt-get install --yes libsystemc libsystemc-dev - fi - fi - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then + PACKAGES=( + ccache + jq + perl + z3 + ) + brew update || brew update - # brew cask install gtkwave # fst2vcd hangs at launch, so don't bother - brew install ccache perl jq z3 - elif [ "$CI_OS_NAME" = "freebsd" ]; then - # fst2vcd fails with "Could not open '', exiting." - sudo pkg install -y ccache gmake perl5 python3 jq z3 + brew install "${PACKAGES[@]}" || + brew install "${PACKAGES[@]}" else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" + fatal "Unknown HOST_OS: '$HOST_OS'" fi # Common installs install-wavediff @@ -146,5 +149,11 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then else ############################################################################## # Unknown build stage - fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'" + fatal "Unknown stage '$STAGE' (expected 'build' or 'test')" fi + +# Report where the tools we may have installed live (ok if some are missing) +for bin in autoconf bear bison ccache flex gdb help2man jq lcov mold perl wavediff z3; do + echo -n "$bin " + which "$bin" || true +done diff --git a/ci/ci-script.bash b/ci/ci-script.bash deleted file mode 100755 index aa7a5987a..000000000 --- a/ci/ci-script.bash +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bash -# DESCRIPTION: Verilator: CI main job script -# -# SPDX-FileCopyrightText: 2020 Geza Lore -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -################################################################################ -# This is the main script executed in the 'script' phase by all jobs. We use a -# single script to keep the CI setting simple. We pass job parameters via -# environment variables using 'env' keys. -################################################################################ - -set -e -set -x - -fatal() { - echo "ERROR: $(basename "$0"): $1" >&2; exit 1; -} - -if [ "$CI_OS_NAME" = "linux" ]; then - export MAKE=make - NPROC=$(nproc) -elif [ "$CI_OS_NAME" = "osx" ]; then - export MAKE=make - NPROC=$(sysctl -n hw.logicalcpu) - # Disable ccache, doesn't always work in GitHub Actions - export OBJCACHE= -elif [ "$CI_OS_NAME" = "freebsd" ]; then - export MAKE=gmake - NPROC=$(sysctl -n hw.ncpu) -else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" -fi -NPROC=$(expr $NPROC '+' 1) - -if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then - ############################################################################## - # Build verilator - - autoconf - CONFIGURE_ARGS="--enable-longtests --enable-ccwarn" - if [ "$CI_DEV_ASAN" = 1 ]; then - CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan" - CXX="$CXX -DVL_LEAK_CHECKS" - fi - if [ "$CI_DEV_GCOV" = 1 ]; then - CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov" - fi - ./configure $CONFIGURE_ARGS --prefix="$INSTALL_DIR" - ccache -z - "$MAKE" -j "$NPROC" -k - # 22.04: ccache -s -v - ccache -s - if [ "$CI_OS_NAME" = "osx" ]; then - file bin/verilator_bin - file bin/verilator_bin_dbg - md5 bin/verilator_bin - md5 bin/verilator_bin_dbg - stat bin/verilator_bin - stat bin/verilator_bin_dbg - fi -elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then - ############################################################################## - # Run tests - - export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check - export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check - - if [ "$CI_OS_NAME" = "osx" ]; then - export VERILATOR_TEST_NO_GDB=1 # Pain to get GDB to work on OS X - # TODO below may no longer be required as configure checks for -pg - export VERILATOR_TEST_NO_GPROF=1 # Apple Clang has no -pg - # export PATH="/Applications/gtkwave.app/Contents/Resources/bin:$PATH" # fst2vcd - file bin/verilator_bin - file bin/verilator_bin_dbg - md5 bin/verilator_bin - md5 bin/verilator_bin_dbg - stat bin/verilator_bin - stat bin/verilator_bin_dbg - # For some reason, the dbg exe is corrupted by this point ('file' reports - # it as data rather than a Mach-O). Unclear if this is an OS X issue or - # CI's. Remove the file and re-link... - rm bin/verilator_bin_dbg - "$MAKE" -j "$NPROC" -k - elif [ "$CI_OS_NAME" = "freebsd" ]; then - export VERILATOR_TEST_NO_GDB=1 # Disable for now, ideally should run - # TODO below may no longer be required as configure checks for -pg - export VERILATOR_TEST_NO_GPROF=1 # gprof is a bit different on FreeBSD, disable - fi - - TEST_REGRESS=test_regress - if [ "$CI_RELOC" == 1 ]; then - # Testing that the installation is relocatable. - "$MAKE" install - mkdir -p "$RELOC_DIR" - mv "$INSTALL_DIR" "$RELOC_DIR/relocated-install" - export VERILATOR_ROOT="$RELOC_DIR/relocated-install/share/verilator" - TEST_REGRESS="$RELOC_DIR/test_regress" - mv test_regress "$TEST_REGRESS" - NODIST="$RELOC_DIR/nodist" - mv nodist "$NODIST" - # Feeling brave? - find . -delete - ls -la . - fi - - # Run the specified test - ccache -z - case $TESTS in - dist-vlt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4 - ;; - dist-vlt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4 - ;; - dist-vlt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4 - ;; - dist-vlt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4 - ;; - vltmt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3 - ;; - vltmt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3 - ;; - vltmt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3 - ;; - coverage-dist) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist" - ;; - coverage-vlt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10 - ;; - coverage-vlt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10 - ;; - coverage-vlt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10 - ;; - coverage-vlt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10 - ;; - coverage-vlt-4) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10 - ;; - coverage-vlt-5) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10 - ;; - coverage-vlt-6) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10 - ;; - coverage-vlt-7) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10 - ;; - coverage-vlt-8) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10 - ;; - coverage-vlt-9) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10 - ;; - coverage-vltmt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10 - ;; - coverage-vltmt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10 - ;; - coverage-vltmt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10 - ;; - coverage-vltmt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10 - ;; - coverage-vltmt-4) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10 - ;; - coverage-vltmt-5) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10 - ;; - coverage-vltmt-6) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10 - ;; - coverage-vltmt-7) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10 - ;; - coverage-vltmt-8) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10 - ;; - coverage-vltmt-9) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10 - ;; - *) - fatal "Unknown TESTS: $TESTS" - ;; - esac - - # To see load average (1 minute, 5 minute, 15 minute) - uptime - # 22.04: ccache -s -v - ccache -s - -else - ############################################################################## - # Unknown build stage - fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'" -fi diff --git a/ci/ci-test.bash b/ci/ci-test.bash new file mode 100755 index 000000000..f35257b94 --- /dev/null +++ b/ci/ci-test.bash @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI test job script +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Executed in the 'test' stage. +################################################################################ + +# Destructive to the checkout (runs 'find . -delete'); never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + +set -e +set -x + +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +OPT_RELOC= +OPT_SUITE= +while [ $# -gt 0 ]; do + case "$1" in + --reloc) + [ $# -ge 2 ] || fatal "--reloc requires an argument" + OPT_RELOC="$2" + shift + ;; + --suite) + [ $# -ge 2 ] || fatal "--suite requires an argument" + OPT_SUITE="$2" + shift + ;; + *) fatal "Unknown option: '$1'" ;; + esac + shift +done + +[ -n "$OPT_SUITE" ] || fatal "--suite is required" + +################################################################################ +# Run tests + +export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check +export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check + +if [ "$HOST_OS" = "macOS" ]; then + export VERILATOR_TEST_NO_GDB=1 # No working GDB on macOS +fi + +TEST_REGRESS=test_regress +if [ -n "$OPT_RELOC" ]; then + # Testing that the installation is relocatable. + "$MAKE" install + # Install prefix, as configured into the Makefile at build time + INSTALL_DIR=$(sed -n 's/^prefix = //p' Makefile) + mkdir -p "$OPT_RELOC" + mv "$INSTALL_DIR" "$OPT_RELOC/relocated-install" + export VERILATOR_ROOT="$OPT_RELOC/relocated-install/share/verilator" + TEST_REGRESS="$OPT_RELOC/test_regress" + mv test_regress "$TEST_REGRESS" + NODIST="$OPT_RELOC/nodist" + mv nodist "$NODIST" + # Delete everything else + find . -delete + ls -la . +fi + +# Run the specified suite +ccache -z +case $OPT_SUITE in + dist-vlt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4 + ;; + dist-vlt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4 + ;; + dist-vlt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4 + ;; + dist-vlt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4 + ;; + vltmt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3 + ;; + vltmt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3 + ;; + vltmt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3 + ;; + coverage-dist) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist" + ;; + coverage-vlt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10 + ;; + coverage-vlt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10 + ;; + coverage-vlt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10 + ;; + coverage-vlt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10 + ;; + coverage-vlt-4) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10 + ;; + coverage-vlt-5) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10 + ;; + coverage-vlt-6) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10 + ;; + coverage-vlt-7) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10 + ;; + coverage-vlt-8) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10 + ;; + coverage-vlt-9) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10 + ;; + coverage-vltmt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10 + ;; + coverage-vltmt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10 + ;; + coverage-vltmt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10 + ;; + coverage-vltmt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10 + ;; + coverage-vltmt-4) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10 + ;; + coverage-vltmt-5) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10 + ;; + coverage-vltmt-6) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10 + ;; + coverage-vltmt-7) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10 + ;; + coverage-vltmt-8) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10 + ;; + coverage-vltmt-9) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10 + ;; + *) + fatal "Unknown suite: $OPT_SUITE" + ;; +esac +ccache -svv +uptime # To see load average From 8d0c75171cef14b015d945a8b6c601b8a1a686f3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 18:52:55 +0100 Subject: [PATCH 09/24] CI: Unify regression and RTLMeter build jobs to one (#7872) - Merge reusable-rtlmeter-build.yml into reusable-build.yml, selected by an 'install' flag: archive the installed Verilator for RTLMeter, or the whole repository tree for the test jobs. - Align reusable-test.yml input arguments with reusable-build.yml - Pass build/test options as booleans instead of 0/1. - Inline the single-entry build matrices, and hoist matrix keys that are constant across a job. --- .github/workflows/build-test.yml | 144 +++++------------- .github/workflows/coverage.yml | 12 +- .github/workflows/reusable-build.yml | 75 ++++++--- .github/workflows/reusable-rtlmeter-build.yml | 99 ------------ .github/workflows/reusable-rtlmeter-run.yml | 4 +- .github/workflows/reusable-test.yml | 33 ++-- .github/workflows/rtlmeter.yml | 24 ++- ci/ci-install.bash | 6 +- 8 files changed, 139 insertions(+), 258 deletions(-) delete mode 100644 .github/workflows/reusable-rtlmeter-build.yml diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e618873cd..e102bc699 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -29,74 +29,61 @@ concurrency: jobs: build-2604-gcc: - name: Build | ubuntu-26.04 | gcc + name: Build | 26.04 | gcc uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: ubuntu-26.04 cc: gcc - dev-asan: 0 - dev-gcov: 0 + runs-on: ubuntu-26.04 + sha: ${{ github.sha }} build-2604-clang: - name: Build | ubuntu-26.04 | clang + name: Build | 26.04 | clang uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: ubuntu-26.04 cc: clang - dev-asan: 1 - dev-gcov: 0 + dev-asan: true # Build (and run) with address sanitizer + runs-on: ubuntu-26.04 + sha: ${{ github.sha }} build-2404-gcc: - name: Build | ubuntu-24.04 | gcc + name: Build | 24.04 | gcc uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: ubuntu-24.04 cc: gcc - dev-asan: 0 - dev-gcov: 0 + runs-on: ubuntu-24.04 + sha: ${{ github.sha }} build-2404-clang: - name: Build | ubuntu-24.04 | clang + name: Build | 24.04 | clang uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: ubuntu-24.04 cc: clang - dev-asan: 0 - dev-gcov: 0 + runs-on: ubuntu-24.04 + sha: ${{ github.sha }} build-2204-gcc: - name: Build | ubuntu-22.04 | gcc + name: Build | 22.04 | gcc uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: ubuntu-22.04 cc: gcc - dev-asan: 0 - dev-gcov: 0 + runs-on: ubuntu-22.04 + sha: ${{ github.sha }} build-macos-15-gcc: name: Build | macos-15 | gcc uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: macos-15 cc: gcc - dev-asan: 0 - dev-gcov: 0 + runs-on: macos-15 + sha: ${{ github.sha }} build-macos-15-clang: name: Build | macos-15 | clang uses: ./.github/workflows/reusable-build.yml with: - sha: ${{ github.sha }} - os: macos-15 cc: clang - dev-asan: 0 - dev-gcov: 0 + runs-on: macos-15 + sha: ${{ github.sha }} build-windows: name: Build | windows-2025-vs2026 | msvc @@ -129,124 +116,75 @@ jobs: name: verilator-win.zip test-2604-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} + name: Test | 26.04 | gcc | ${{ matrix.suite }} needs: build-2604-gcc uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2604-gcc.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-26.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 26.04 gcc - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2604-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} + name: Test | 26.04 | clang | ${{ matrix.suite }} needs: build-2604-clang uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2604-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: clang + runs-on: ubuntu-26.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 26.04 clang - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2404-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} + name: Test | 24.04 | gcc | ${{ matrix.suite }} needs: build-2404-gcc uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2404-gcc.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-24.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 24.04 gcc - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2404-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} + name: Test | 24.04 | clang | ${{ matrix.suite }} needs: build-2404-clang uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2404-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: clang + reloc: true # Test with relocated installation + runs-on: ubuntu-24.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 24.04 clang, also test relocation - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 1, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2204-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.suite }} + name: Test | 22.04 | gcc | ${{ matrix.suite }} needs: build-2204-gcc uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2204-gcc.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-22.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 22.04 gcc - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] lint-py: name: Lint Python diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3b21b8f48..00ab5329e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,14 +38,13 @@ jobs: (github.event_name == 'workflow_dispatch') uses: ./.github/workflows/reusable-build.yml with: + cc: gcc + dev-gcov: true + runs-on: ubuntu-24.04 # For pull requests, build the head of the pull request branch, not the # merge commit, otherwise patch coverage would include the changes # between the root of the pull request and the target branch sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - os: ubuntu-24.04 - cc: gcc - dev-asan: 0 - dev-gcov: 1 test: name: Test | ${{ matrix.test }}${{ matrix.num }} @@ -53,11 +52,10 @@ jobs: uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build.outputs.archive }} - os: ubuntu-24.04 cc: gcc - reloc: 0 + dev-gcov: true + runs-on: ubuntu-24.04 suite: ${{ matrix.test }}${{ matrix.num }} - dev-gcov: 1 strategy: fail-fast: false matrix: diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index a956d927e..4b26610bc 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -7,25 +7,41 @@ name: reusable-build on: workflow_call: inputs: + cc: + description: "Compiler to use: 'gcc' or 'clang'" + required: true + type: string + ccwarn: + description: "Build Verilator with warnings treated as errors (--enable-ccwarn)" + required: false + default: true + type: boolean + dev-asan: + description: "Build Verilator with the address sanitizer (--enable-dev-asan)" + required: false + default: false + type: boolean + dev-gcov: + description: "Build Verilator with gcov instrumentation (--enable-dev-gcov)" + required: false + default: false + type: boolean + install: + description: "Archive the Verilator installation, not the repo tree" + required: false + default: false + type: boolean + runs-on: + description: "Runner to build on, e.g. ubuntu-24.04" + required: true + type: string sha: description: "Commit SHA to build" required: true type: string - os: # e.g. ubuntu-24.04 - required: true - type: string - cc: # 'clang' or 'gcc' - required: true - type: string - dev-asan: - required: true - type: number - dev-gcov: - required: true - type: number outputs: archive: - description: "Name of the built repository archive artifact" + description: "Name of the built archive artifact" value: ${{ jobs.build.outputs.archive }} env: @@ -42,19 +58,21 @@ jobs: build: name: Build - runs-on: ${{ inputs.os }} + runs-on: ${{ inputs.runs-on }} outputs: archive: ${{ steps.create-archive.outputs.archive }} env: - CACHE_BASE_KEY: build-${{ inputs.os }}-${{ inputs.cc }} - CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) + CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }} + CCACHE_MAXSIZE: 1000M steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} - fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history + # Coverage needs full history; install needs it for the 'git describe' + # behind 'verilator --version' + fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }} - name: Cache $CCACHE_DIR uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 @@ -74,24 +92,33 @@ jobs: ./ci/ci-build.bash \ --prefix ${{ github.workspace }}/install \ --compiler ${{ inputs.cc }} \ - --ccwarn \ + ${{ inputs.ccwarn && '--ccwarn' || '' }} \ ${{ inputs.dev-asan && '--asan' || '' }} \ ${{ inputs.dev-gcov && '--gcov' || '' }} - - name: Create repository archive + - name: Install + if: ${{ inputs.install }} + run: make install + + - name: Create archive id: create-archive working-directory: ${{ github.workspace }} run: | - # Name of the archive must be unique based on the build parameters - ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.zst - # zstd compresses faster than gzip and decompresses far faster in the many downstream test jobs - ZSTD_NBTHREADS=0 tar --posix --zstd -c -f $ARCHIVE repo + # Archive name, unique per build; flavour tags mark non-default builds + ARCHIVE="verilator-${{ inputs.sha }}-${{ inputs.runs-on }}-${{ inputs.cc }}" + ARCHIVE="$ARCHIVE${{ inputs.ccwarn && '-ccwarn' || '' }}" + ARCHIVE="$ARCHIVE${{ inputs.dev-asan && '-asan' || '' }}" + ARCHIVE="$ARCHIVE${{ inputs.dev-gcov && '-gcov' || '' }}" + ARCHIVE="$ARCHIVE.tar.zst" + # zstd compresses faster than gzip and decompresses far faster in the many downstream jobs + ZSTD_NBTHREADS=0 tar --posix --zstd -c -f "$ARCHIVE" ${{ inputs.install && 'install' || 'repo' }} echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - - name: Upload repository archive + - name: Upload archive uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} + overwrite: true # Archive is already zstd-compressed; skip upload-artifact's zip pass compression-level: 0 diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml deleted file mode 100644 index 1c0078207..000000000 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ /dev/null @@ -1,99 +0,0 @@ ---- -# DESCRIPTION: Github actions config -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -name: reusable-rtlmeter-build - -on: - workflow_call: - inputs: - runs-on: - description: "Runner to use, e.g.: ubuntu-24.04" - type: string - required: true - cc: - description: "Compiler to use: 'gcc' or 'clang'" - type: string - required: true - sha: - description: "Git SHA to build" - type: string - required: true - outputs: - archive: - description: "Name of the built installation archive artifact" - value: ${{ jobs.build.outputs.archive }} - -defaults: - run: - shell: bash - -env: - CCACHE_DIR: ${{ github.workspace }}/ccache - CCACHE_MAXSIZE: 512M - -jobs: - build: - name: Build - runs-on: ${{ inputs.runs-on }} - outputs: - archive: ${{ steps.create-archive.outputs.archive }} - steps: - - name: Install dependencies - run: | - echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - sudo apt update || \ - sudo apt update - sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev || \ - sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev - - - name: Use saved ccache - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 - with: - path: ccache - key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }} - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }} - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - with: - path: repo - ref: ${{ inputs.sha }} - fetch-depth: 0 # Required for 'git describe' used for 'verilator --version' - - - name: Configure - working-directory: repo - run: | - autoconf - ./configure --prefix=${{ github.workspace }}/install CXX=${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - - - name: Make - working-directory: repo - run: make -j $(nproc) - - - name: Install - working-directory: repo - run: make install - - - name: Tar up installation - id: create-archive - run: | - SHA=$(git -C repo rev-parse HEAD) - ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.zst - # zstd compresses faster than gzip and decompresses far faster in the downstream run jobs - ZSTD_NBTHREADS=0 tar --posix --zstd -c -f $ARCHIVE install - echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - - - name: Upload Verilator installation archive - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - path: ${{ steps.create-archive.outputs.archive }} - name: ${{ steps.create-archive.outputs.archive }} - overwrite: true - # Archive is already zstd-compressed; skip upload-artifact's zip pass - compression-level: 0 diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index c4dfbf913..55cc4bc55 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -20,11 +20,11 @@ on: type: string required: true verilator-archive-new: - description: "Name of the installation archive artifact from reusable-rtlmeter-build, new version" + description: "Name of the installation archive artifact from reusable-build, new version" type: string required: true verilator-archive-old: - description: "Name of the installation archive artifact from reusable-rtlmeter-build, old version" + description: "Name of the installation archive artifact from reusable-build, old version" type: string required: false default: "" diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 1c393c4f0..8782f17e4 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -11,21 +11,28 @@ on: description: "Name of the repository archive artifact from reusable-build" required: true type: string - os: # e.g. ubuntu-24.04 - required: true - type: string - cc: # gcc or clang - required: true - type: string - reloc: # 0 or 1 - required: true - type: number - suite: # e.g. dist-vlt-0 + cc: + description: "Compiler to use: 'gcc' or 'clang'" required: true type: string dev-gcov: + description: "Collect gcov coverage data from the test run" + required: false + default: false + type: boolean + reloc: + description: "Relocate the installation before testing" + required: false + default: false + type: boolean + runs-on: + description: "Runner to test on, e.g. ubuntu-24.04" required: true - type: number + type: string + suite: + description: "Test suite to run, e.g. dist-vlt-0" + required: true + type: string env: CCACHE_COMPRESS: 1 @@ -40,11 +47,11 @@ defaults: jobs: test: - runs-on: ${{ inputs.os }} + runs-on: ${{ inputs.runs-on }} name: Test env: CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - CACHE_BASE_KEY: test-${{ inputs.os }}-${{ inputs.cc }}-${{inputs.reloc }}-${{ inputs.suite }} + CACHE_BASE_KEY: test-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.reloc }}-${{ inputs.suite }} CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total) steps: diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index 1848d4ec8..c0f7506cd 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -65,39 +65,47 @@ jobs: build-gcc-new: name: Build New Verilator - GCC needs: start - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: gcc + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ github.sha }} build-clang-new: name: Build New Verilator - Clang needs: start - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: clang + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ github.sha }} build-gcc-old: name: Build Old Verilator - GCC needs: start if: ${{ needs.start.outputs.old-sha != '' }} - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: gcc + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ needs.start.outputs.old-sha }} build-clang-old: name: Build Old Verilator - Clang needs: start if: ${{ needs.start.outputs.old-sha != '' }} - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: clang + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ needs.start.outputs.old-sha }} run-gcc: diff --git a/ci/ci-install.bash b/ci/ci-install.bash index db71c8159..1f58184ea 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -153,7 +153,9 @@ else fi # Report where the tools we may have installed live (ok if some are missing) +set +x +echo "Tools:" for bin in autoconf bear bison ccache flex gdb help2man jq lcov mold perl wavediff z3; do - echo -n "$bin " - which "$bin" || true + echo -n " $bin: " + which "$bin" || echo "Not found" done From cbd78e689c5c3ec5588f080e17f77ab0ce525f11 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 19:15:09 +0100 Subject: [PATCH 10/24] CI: Cleanup Windows build (#7873) The Windows build never actually used ccache; the '.ccache' directory only ever held the win_flex_bison install, and cmake/MSVC did no compiler caching. Clean this up: - Drop the CCACHE_* environment variables from the job (meaningless here). - Store win_flex_bison in a dedicated 'win_flex_bison' directory instead of the misleadingly named '.ccache', and cache just that (static key, as it is a fixed third-party tool). - Set WIN_FLEX_BISON inside ci-win-compile.ps1 as an absolute path, rather than in the workflow, making the script self-contained. - Use the runner's processor count for build parallelism instead of a fixed '-j 3', in both ci-win-compile.ps1 and ci-win-test.ps1. - Remove the verilator-win.zip archive and upload steps; nothing consumes that artifact. - Build Verilator with the Ninja generator from an MSVC developer shell instead of MSBuild. MSBuild only parallelizes across projects, and Verilator is a single project, so it compiled serially; Ninja keeps all cores busy. Also compile with /Od: this job only checks that Verilator builds and can verilate an example, so an optimized binary is not needed and the otherwise-dominant optimizer time is wasted. --- .github/workflows/build-test.yml | 19 +++---------------- ci/ci-win-compile.ps1 | 26 ++++++++++++++++++++------ ci/ci-win-test.ps1 | 8 ++++++-- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e102bc699..5b2507640 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -88,32 +88,19 @@ jobs: build-windows: name: Build | windows-2025-vs2026 | msvc runs-on: windows-2025-vs2026 - env: - CCACHE_COMPRESS: 1 - CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - - name: Cache $CCACHE_DIR + - name: Cache win_flex_bison uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 with: - path: ${{ env.CCACHE_DIR }} - key: msbuild-msvc-cmake + path: ${{ github.workspace }}/win_flex_bison + key: win_flex_bison - name: compile - env: - WIN_FLEX_BISON: ${{ github.workspace }}/.ccache run: ./ci/ci-win-compile.ps1 - name: test build run: ./ci/ci-win-test.ps1 - - name: Zip up repository - run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip - - name: Upload zip archive - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - path: ${{ github.workspace }}/repo/verilator.zip - name: verilator-win.zip test-2604-gcc: name: Test | 26.04 | gcc | ${{ matrix.suite }} diff --git a/ci/ci-win-compile.ps1 b/ci/ci-win-compile.ps1 index 2722d8362..8f019b5e5 100644 --- a/ci/ci-win-compile.ps1 +++ b/ci/ci-win-compile.ps1 @@ -6,19 +6,33 @@ Set-PSDebug -Trace 1 -if (-Not (Test-Path $PWD/../.ccache/win_bison.exe)) { +$NPROC = $env:NUMBER_OF_PROCESSORS + +# Absolute path for the win_flex_bison install; CMake reads this from the environment +$env:WIN_FLEX_BISON = "$(Resolve-Path ..)/win_flex_bison" + +if (-Not (Test-Path $env:WIN_FLEX_BISON/win_bison.exe)) { git clone --depth 1 https://github.com/lexxmark/winflexbison cd winflexbison mkdir build cd build - cmake .. --install-prefix $PWD/../../../.ccache - cmake --build . --config Release -j 3 - cmake --install . --prefix $PWD/../../../.ccache + cmake .. --install-prefix $env:WIN_FLEX_BISON + cmake --build . --config Release -j $NPROC + cmake --install . --prefix $env:WIN_FLEX_BISON cd ../.. } +# Enter the MSVC developer shell so cl/link are on PATH for the Ninja generator +$VsPath = & "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" -latest -products * -property installationPath +Import-Module "$VsPath/Common7/Tools/Microsoft.VisualStudio.DevShell.dll" +Enter-VsDevShell -VsInstallPath $VsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64" + mkdir build cd build -cmake .. --install-prefix $PWD/../install -cmake --build . --config Release -j 3 +# Ninja saturates all cores; the MSBuild generator only parallelizes across +# projects and Verilator is a single target, so it compiles serially. /Od skips +# optimization: this job only checks that Verilator builds and can verilate an +# example, so an optimized binary is not needed and the codegen time dominates. +cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release --install-prefix $PWD/../install "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG" +cmake --build . cmake --install . --prefix $PWD/../install diff --git a/ci/ci-win-test.ps1 b/ci/ci-win-test.ps1 index 23697fe09..288bcbe37 100644 --- a/ci/ci-win-test.ps1 +++ b/ci/ci-win-test.ps1 @@ -7,13 +7,17 @@ Set-PSDebug -Trace 1 +$NPROC = $env:NUMBER_OF_PROCESSORS + cd install $Env:VERILATOR_ROOT=$PWD cd examples/cmake_tracing_c mkdir build cd build -cmake .. -cmake --build . --config Release -j 3 +# /Od skips optimization; this only checks the example verilates and builds, so +# an optimized binary is not needed (see ci-win-compile.ps1). +cmake .. "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG" +cmake --build . --config Release -j $NPROC # TODO put this back in, see issue# 5163 # Release/example.exe From c7c778f109b3f13cba5b28a4587f8d5fafb2b11d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 20:06:03 +0100 Subject: [PATCH 11/24] CI: Improve Pages job Code coverage and RTLMeter complete (concluding 'skipped') on branch pushes where they do no work, and each completion fires the Pages workflow_run trigger. Rebuilding and redeploying master's pages for these no-op events is wasteful. Gate the build job so a workflow_run whose upstream conclusion is 'skipped' does nothing; deploy and notify need build, so they cascade-skip. Route such no-op runs to their own throwaway concurrency group. In the shared 'pages' group they would, as the newest queued run, cancel a pending real run and then deploy nothing (concurrency keeps only the in-progress and newest pending run). Real runs keep sharing 'pages' and serialize as before. Add retry to deploy step to reduce flakyness due to the Pages back end. Fix trigger on workflow file changes. --- .github/workflows/pages.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 59048157c..e1f258832 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -7,7 +7,7 @@ name: Pages on: push: branches: [master] - paths: ["ci/**", ".github/workflows"] + paths: ["ci/**", ".github/workflows/**"] workflow_dispatch: workflow_run: workflows: ["Code coverage", "RTLMeter"] @@ -22,8 +22,11 @@ permissions: # Allow only one concurrent deployment, skipping runs queued between the run # in-progress and latest queued. However, do NOT cancel in-progress runs as we # want to allow these deployments to complete. +# A skipped-upstream run does no work (see the build job's if:), so put it in its +# own throwaway group: otherwise, as the newest queued run, it would cancel a +# pending real run and then deploy nothing. concurrency: - group: "pages" + group: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'skipped') && format('pages-skip-{0}', github.run_id) || 'pages' }} cancel-in-progress: false defaults: @@ -34,6 +37,10 @@ jobs: build: name: Build content runs-on: ubuntu-24.04 + # A skipped upstream run (e.g. Code coverage / RTLMeter on a branch push) + # still fires workflow_run; don't rebuild and redeploy for it. deploy and + # notify need this job, so they cascade-skip too. + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'skipped' }} outputs: pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: @@ -58,9 +65,16 @@ jobs: runs-on: ubuntu-24.04 environment: name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + url: ${{ steps.deploy-2.outputs.page_url || steps.deploy-1.outputs.page_url }} steps: + # GitHub's Pages backend intermittently fails a deployment mid-sync, retry - name: Deploy to GitHub Pages + id: deploy-1 + continue-on-error: true + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 + - name: Deploy to GitHub Pages (retry) + id: deploy-2 + if: steps.deploy-1.outcome == 'failure' uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 notify: From 8b564f509bd29f5879d48fb894c6f40cc3aeaff5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 5 Jul 2026 21:17:09 +0100 Subject: [PATCH 12/24] CI: Remove macos-15 gcc job On macOS, gcc is an alias for clang, so this job was effectively the same as the clang job. --- .github/workflows/build-test.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5b2507640..0e6f05cc1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -69,14 +69,6 @@ jobs: runs-on: ubuntu-22.04 sha: ${{ github.sha }} - build-macos-15-gcc: - name: Build | macos-15 | gcc - uses: ./.github/workflows/reusable-build.yml - with: - cc: gcc - runs-on: macos-15 - sha: ${{ github.sha }} - build-macos-15-clang: name: Build | macos-15 | clang uses: ./.github/workflows/reusable-build.yml From 5750c888a69018727c69af4bbfc3d5c3191d410d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 01:44:32 +0100 Subject: [PATCH 13/24] CI: Improve ccache handling in build and test jobs (#7874) Cache key isolation: Include the build flavour (ccwarn/asan/gcov) in the build ccache key, so the coverage (gcov), asan, and RTLMeter (ccwarn-off) configurations no longer share and thrash the single '-' bucket, nor warm-start from each other's (near-useless) caches. Prune stale entries instead of a fixed size cap: After building/testing, evict ccache entries this run did not touch (--evict-older-than, based on the run's own duration). This bounds the saved cache to the current working set, so CCACHE_MAXSIZE and CCACHE_LIMIT_MULTIPLE are no longer needed and are removed (ccache falls back to its 5G default ceiling, but is realistically much smaller). ccache configuration: - Drop CCACHE_COMPRESS (already the default in ccache 4.x). - Add CCACHE_COMPILERCHECK=content, so a changed compiler mtime on a rebuilt ephemeral runner image does not invalidate the whole restored cache. - Hoist all ccache-related variables (CACHE_BASE_KEY, CCACHE_*, CXX) to the workflow-level env. Remove dead ccache scaffolding: - reusable-lint-py: the job never compiles or caches; drop its ccache env. - reusable-rtlmeter-run: remove the disabled persistent-cache step and its storage variables; keep CCACHE_DISABLE=1, since caching must stay off to keep RTLMeter timings representative. Store test job ccache as artifacts for reuse --- .github/workflows/build-test.yml | 1 + .github/workflows/coverage.yml | 1 + .github/workflows/reusable-build.yml | 15 +-- .github/workflows/reusable-lint-py.yml | 5 - .github/workflows/reusable-rtlmeter-run.yml | 12 +-- .github/workflows/reusable-test.yml | 101 +++++++++++++++++--- ci/ci-build.bash | 3 + ci/ci-common.bash | 2 - ci/ci-test.bash | 3 + 9 files changed, 106 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0e6f05cc1..163669117 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -16,6 +16,7 @@ on: permissions: contents: read + actions: read defaults: run: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 00ab5329e..5ff39b99b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,6 +13,7 @@ on: permissions: contents: read + actions: read defaults: run: diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 4b26610bc..06b668c2d 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -45,9 +45,9 @@ on: value: ${{ jobs.build.outputs.archive }} env: - CCACHE_COMPRESS: 1 + CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }}${{ inputs.ccwarn && '-ccwarn' || '' }}${{ inputs.dev-asan && '-asan' || '' }}${{ inputs.dev-gcov && '-gcov' || '' }} + CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 defaults: run: @@ -61,9 +61,6 @@ jobs: runs-on: ${{ inputs.runs-on }} outputs: archive: ${{ steps.create-archive.outputs.archive }} - env: - CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }} - CCACHE_MAXSIZE: 1000M steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 @@ -74,7 +71,13 @@ jobs: # behind 'verilator --version' fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }} + # ccache is unreliable on the macOS runners; disable it there + - name: Disable ccache on macOS + if: ${{ startsWith(inputs.runs-on, 'macos') }} + run: echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + - name: Cache $CCACHE_DIR + if: ${{ env.CCACHE_DISABLE != '1' }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache @@ -82,7 +85,7 @@ jobs: path: ${{ env.CCACHE_DIR }} key: ${{ env.CACHE_KEY }}-${{ inputs.sha }} restore-keys: | - ${{ env.CACHE_KEY }}- + ${{ env.CACHE_KEY }} - name: Install packages for build run: ./ci/ci-install.bash build diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index ae7ff30b6..be0e60cd3 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -7,11 +7,6 @@ name: reusable-lint-py on: workflow_call: -env: - CCACHE_COMPRESS: 1 - CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 - defaults: run: shell: bash diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index 55cc4bc55..4d8fbd9b1 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -53,8 +53,8 @@ defaults: shell: bash env: - CCACHE_DIR: ${{ github.workspace }}/ccache - CCACHE_MAXSIZE: 512M + # RTLMeter measures compile/execute times, so caching must stay off to keep + # timings representative; ccache is still on PATH but acts as a pass-through CCACHE_DISABLE: 1 jobs: @@ -82,14 +82,6 @@ jobs: working-directory: rtlmeter run: make venv - - name: Use saved ccache - if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 - with: - path: ${{ env.CCACHE_DIR }} - key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }} - ######################################################################## # Run with new Verilator ######################################################################## diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 8782f17e4..8964cabd2 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -34,10 +34,15 @@ on: required: true type: string +permissions: + contents: read + actions: read + env: - CCACHE_COMPRESS: 1 + CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 + CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} + GH_TOKEN: ${{ github.token }} defaults: run: @@ -49,10 +54,6 @@ jobs: test: runs-on: ${{ inputs.runs-on }} name: Test - env: - CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - CACHE_BASE_KEY: test-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.reloc }}-${{ inputs.suite }} - CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total) steps: - name: Download repository archive @@ -67,15 +68,73 @@ jobs: tar --zstd -x -f ${{ inputs.archive }} ls -lsha - - name: Cache $CCACHE_DIR - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 + # Test-job ccache is stored as an artifact, not actions/cache + # - pull_request: restore this PR's own previous run; if none, fall back to the PR's target branch. + # - branch push: restore that branch's own cache + # Artifact names are a global, unauthenticated namespace, so a PR could + # upload a "-branch-" artifact impersonating a branch. Branch-scoped + # restores are therefore provenance-checked below (must come from a same-repo + # push to that branch); the pr- scope is not (a PR can only affect its own + # unmerged cache). + - name: Compute ccache artifact name + # Pass github refs via env, not ${{ }} interpolation, so a branch name + # containing shell metacharacters can't break or inject into the script. env: - CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2 - with: - path: ${{ env.CCACHE_DIR }} - key: ${{ env.CACHE_KEY }}-${{ github.sha }} - restore-keys: | - ${{ env.CACHE_KEY }}- + BASE_REF: ${{ github.event.pull_request.base.ref }} + REF_NAME: ${{ github.ref_name }} + run: | + base="ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }}" + sanitize() { printf '%s' "$1" | tr '/' '_'; } # artifact names cannot contain '/' + if [ "${{ github.event_name }}" = pull_request ]; then + echo "CCACHE_ARTIFACT=${base}-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_ENV" + echo "CCACHE_ARTIFACT_BRANCH=" >> "$GITHUB_ENV" + echo "CCACHE_FALLBACK=${base}-branch-$(sanitize "$BASE_REF")" >> "$GITHUB_ENV" + echo "CCACHE_FALLBACK_BRANCH=${BASE_REF}" >> "$GITHUB_ENV" + else + echo "CCACHE_ARTIFACT=${base}-branch-$(sanitize "$REF_NAME")" >> "$GITHUB_ENV" + echo "CCACHE_ARTIFACT_BRANCH=${REF_NAME}" >> "$GITHUB_ENV" + fi + + - name: Restore ccache from artifact + continue-on-error: true # a cold start is fine; never fail the job here + run: | + set -euo pipefail + mkdir -p "$CCACHE_DIR" + # Return newest non-expired artifact named $1 (empty if none). When + # $2 (branch) is non-empty, require provenance: the artifact must come from + # a same-repo run (head_repository_id == repository_id) whose head branch + # is $2, so a fork PR cannot forge a branch-scoped artifact. + newest_run_id() { + want="$1" br="$2" gh api \ + "/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \ + --jq '.artifacts[] + | select(.expired == false and .name == env.want) + | select(env.br == "" + or (.workflow_run.head_branch == env.br + and .workflow_run.head_repository_id == .workflow_run.repository_id)) + | [.created_at, (.workflow_run.id | tostring)] | @tsv' \ + | sort | tail -n1 | cut -f2 + } + restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore + local name="$1" br="$2" rid tarball + [ -n "$name" ] || return 1 + rid="$(newest_run_id "$name" "$br")" || return 1 + [ -n "$rid" ] || return 1 + echo "Restoring ccache from '$name' (run $rid)" + gh run download "$rid" --name "$name" --dir "$RUNNER_TEMP/ccache-dl" || return 1 + tarball="$(find "$RUNNER_TEMP/ccache-dl" -name ccache.tar.zst -print -quit)" + [ -n "$tarball" ] || return 1 + tar -I zstd -x -f "$tarball" -C "$CCACHE_DIR" + } + if restore "${CCACHE_ARTIFACT:-}" "${CCACHE_ARTIFACT_BRANCH:-}"; then + echo "Restored primary ccache" + exit 0 + fi + if restore "${CCACHE_FALLBACK:-}" "${CCACHE_FALLBACK_BRANCH:-}"; then + echo "Restored fallback ccache" + exit 0 + fi + echo "No ccache artifact found; starting cold" - name: Install test dependencies run: | @@ -105,6 +164,20 @@ jobs: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} + - name: Pack ccache + if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} + run: tar -I 'zstd -T0' -cf "$GITHUB_WORKSPACE/ccache.tar.zst" -C "$CCACHE_DIR" . + + - name: Save ccache artifact + if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ env.CCACHE_ARTIFACT }} + path: ${{ github.workspace }}/ccache.tar.zst + retention-days: 3 + overwrite: true + compression-level: 0 + - name: Fail job if a test failed if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }} run: |- diff --git a/ci/ci-build.bash b/ci/ci-build.bash index 722ed6563..e9c2b2a20 100755 --- a/ci/ci-build.bash +++ b/ci/ci-build.bash @@ -78,5 +78,8 @@ autoconf # Build ccache -z +BUILD_START=$SECONDS "$MAKE" -j "$NPROC" -k ccache -svv +ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s" +ccache -svv diff --git a/ci/ci-common.bash b/ci/ci-common.bash index ef2fe23c8..1bc8826a3 100644 --- a/ci/ci-common.bash +++ b/ci/ci-common.bash @@ -26,8 +26,6 @@ case "$(uname -s)" in HOST_OS=macOS MAKE=make NPROC=$(sysctl -n hw.logicalcpu) - # Disable ccache, doesn't always work in GitHub Actions - export OBJCACHE= ;; *) fatal "Unknown host OS: '$(uname -s)'" diff --git a/ci/ci-test.bash b/ci/ci-test.bash index f35257b94..52183f7bf 100755 --- a/ci/ci-test.bash +++ b/ci/ci-test.bash @@ -73,6 +73,7 @@ fi # Run the specified suite ccache -z +TEST_START=$SECONDS case $OPT_SUITE in dist-vlt-0) "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4 @@ -163,4 +164,6 @@ case $OPT_SUITE in ;; esac ccache -svv +ccache --evict-older-than "$((SECONDS - TEST_START + 60))s" +ccache -svv uptime # To see load average From 5563b3b6f858fd2a8261e4ce009499e8646115c6 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 11:03:35 +0100 Subject: [PATCH 14/24] CI: Cache Python venv (#7879) This is smallish and mostly static, should cache well, avoid setup cost. --- .github/actions/setup-venv/action.yml | 55 ++++++++++++++++++++++++++ .github/workflows/format.yml | 19 ++++++++- .github/workflows/reusable-lint-py.yml | 16 ++++---- .github/workflows/reusable-test.yml | 6 ++- python-dev-requirements.txt | 2 +- 5 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 .github/actions/setup-venv/action.yml diff --git a/.github/actions/setup-venv/action.yml b/.github/actions/setup-venv/action.yml new file mode 100644 index 000000000..f1d2b3024 --- /dev/null +++ b/.github/actions/setup-venv/action.yml @@ -0,0 +1,55 @@ +--- +# DESCRIPTION: Github actions composite action +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +name: Set up Python venv +description: >- + Restore, create, and optionally save the make-managed Python venv in a GitHub + Actions cache. The cache key combines the venv path (venvs are not + relocatable), the host OS and version, the Python major.minor version, and + the python-dev-requirements.txt hash, so any of those changing invalidates + it. Requires the already-configured Verilator tree to be present in 'repo'. + +inputs: + repo: + description: "Verilator checkout holding the Makefile and .venv" + required: false + default: repo + save: + description: "Save the cache on a miss (set true only on trusted branches)" + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Compute Python venv cache key + id: cachekey + shell: bash + working-directory: ${{ inputs.repo }} + run: | + source ci/ci-common.bash + host="${DISTRO_ID:-$HOST_OS}${DISTRO_VERSION:+-$DISTRO_VERSION}" + pyver="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + reqs="$(sha256sum python-dev-requirements.txt | cut -d' ' -f1)" + venv="${PWD#/}/.venv" # absolute venv path (leading '/' trimmed) + echo "key=venv-${host}-py${pyver}-${reqs}-${venv//\//-}" >> "$GITHUB_OUTPUT" + + - name: Restore Python venv + id: venv + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 + with: + path: ${{ inputs.repo }}/.venv + key: ${{ steps.cachekey.outputs.key }} + + - name: Create Python venv + shell: bash + working-directory: ${{ inputs.repo }} + run: make venv + + - name: Save Python venv + if: ${{ inputs.save == 'true' && steps.venv.outputs.cache-hit != 'true' }} + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 + with: + path: ${{ inputs.repo }}/.venv + key: ${{ steps.venv.outputs.cache-primary-key }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 693190453..78fd4130a 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,6 +11,10 @@ on: permissions: contents: write +defaults: + run: + working-directory: repo + jobs: format: runs-on: ubuntu-24.04 @@ -19,21 +23,32 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + path: repo token: ${{ secrets.GITHUB_TOKEN }} + - name: Install packages for build run: | sudo apt install clang-format-18 || \ sudo apt install clang-format-18 git config --global user.email "action@example.com" git config --global user.name "github action" - - name: Format code + + - name: Configure run: | autoconf ./configure - make venv + + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} + + - name: Format code + run: | source .venv/bin/activate make -j 4 format CLANGFORMAT=clang-format-18 git status + - name: Push run: |- if [ -n "$(git status --porcelain)" ]; then diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index be0e60cd3..f30e4898b 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -23,19 +23,21 @@ jobs: with: path: repo - - name: Install packages for build - run: ./ci/ci-install.bash build + - name: Install dependencies + run: | + ./ci/ci-install.bash build + sudo apt install python3-clang || \ + sudo apt install python3-clang - name: Configure run: | autoconf ./configure --enable-longtests --enable-ccwarn - - name: Install python dependencies - run: | - sudo apt install python3-clang || \ - sudo apt install python3-clang - make venv + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} - name: Lint run: |- diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 8964cabd2..dd8ed87f7 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -139,7 +139,11 @@ jobs: - name: Install test dependencies run: | ./ci/ci-install.bash test - make venv + + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} - name: Test id: run-test diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 3e33332cc..e90349d2c 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -33,4 +33,4 @@ sphinxcontrib-spelling==8.0.2 yamlfix==1.19.1 yapf==0.43.0 -git+https://github.com/antmicro/astsee.git +git+https://github.com/antmicro/astsee.git@222480a8ec13b312809ea4acc08c81b4c0f4da2f From c2ed0aab0065bdb849acb6f7c06e141822c61954 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 11:27:58 +0100 Subject: [PATCH 15/24] CI: Use ci-install.bash for lint-py dependencies (#7880) --- .github/workflows/reusable-lint-py.yml | 5 +---- ci/ci-install.bash | 13 ++++++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index f30e4898b..e3bea6fe7 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -24,10 +24,7 @@ jobs: path: repo - name: Install dependencies - run: | - ./ci/ci-install.bash build - sudo apt install python3-clang || \ - sudo apt install python3-clang + run: ./ci/ci-install.bash lint-py - name: Configure run: | diff --git a/ci/ci-install.bash b/ci/ci-install.bash index 1f58184ea..04ce8958b 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -146,10 +146,21 @@ elif [ "$STAGE" = "test" ]; then install-wavediff # Workaround -fsanitize=address crash sudo sysctl -w vm.mmap_rnd_bits=28 +elif [ "$STAGE" = "lint-py" ]; then + # nodist/clang_check_attributes. + if [ "$HOST_OS" = "linux" ] && [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + python3-clang # Not run, but importers are linted + ) + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" + fi else ############################################################################## # Unknown build stage - fatal "Unknown stage '$STAGE' (expected 'build' or 'test')" + fatal "Unknown stage '$STAGE' (expected 'build', 'test' or 'lint-py')" fi # Report where the tools we may have installed live (ok if some are missing) From c78a342bdc6b136036f2f1b12d2477094a5cd301 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 11:30:33 +0100 Subject: [PATCH 16/24] CI: Don't Python make venv on cache hit --- .github/actions/setup-venv/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-venv/action.yml b/.github/actions/setup-venv/action.yml index f1d2b3024..3feb9c5be 100644 --- a/.github/actions/setup-venv/action.yml +++ b/.github/actions/setup-venv/action.yml @@ -43,6 +43,7 @@ runs: key: ${{ steps.cachekey.outputs.key }} - name: Create Python venv + if: ${{ steps.venv.outputs.cache-hit != 'true' }} shell: bash working-directory: ${{ inputs.repo }} run: make venv From bf4c72f93b4d28919fc36fde8992e8eb656d982d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 11:43:55 +0100 Subject: [PATCH 17/24] CI: Squash macos brew warning (#7881) --- ci/ci-install.bash | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci/ci-install.bash b/ci/ci-install.bash index 04ce8958b..fa14be495 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -41,6 +41,11 @@ if [ "$HOST_OS" = "linux" ]; then echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc +elif [ "$HOST_OS" = "macOS" ]; then + # The macos runner image ships an untrusted third-party tap we don't use; + # untap it so brew stops emitting a tap-trust warning. Force + '|| true' since + # untap fails if a formula was installed from it, which is harmless here. + brew untap --force aws/tap || true fi install-wavediff() { From fcc7acbfc64f32a043938f50ea88011e47ae49f3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 14:16:06 +0100 Subject: [PATCH 18/24] CI: Factor out artifact based caching as a reusable action (#7883) --- .github/actions/artifact-cache/action.yml | 165 ++++++++++++++++++++++ .github/workflows/reusable-test.yml | 94 ++---------- ci/ci-test.bash | 6 +- 3 files changed, 183 insertions(+), 82 deletions(-) create mode 100644 .github/actions/artifact-cache/action.yml diff --git a/.github/actions/artifact-cache/action.yml b/.github/actions/artifact-cache/action.yml new file mode 100644 index 000000000..0fcc4448b --- /dev/null +++ b/.github/actions/artifact-cache/action.yml @@ -0,0 +1,165 @@ +--- +# DESCRIPTION: Github actions composite action +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +name: Artifact cache +description: >- + Cache a directory across workflow runs using a GitHub Actions artifact instead + of actions/cache (useful where artifacts are preferable, e.g. no 10 GB limit + on public repos). The base 'key' is scoped automatically: per pull request + (-pr-) or per branch (-branch-), and on a PR restore falls + back to the target branch's cache. Call once with mode=restore before the work + and once with mode=save after, passing the same 'key' and 'path'. + + Branch-scoped restores are provenance-checked (the artifact must come from a + same-repo push to that branch) - artifact names are a global, unauthenticated + namespace, so otherwise a fork PR could forge one. + + mode=restore calls the GitHub API via 'gh', so the caller's job must grant + 'actions: read'. + +inputs: + mode: + description: "'restore' or 'save'" + required: true + path: + description: "Directory to cache (archived on save, extracted into on restore)" + required: true + key: + description: "Base artifact name; scoped per PR/branch automatically" + required: true + retention-days: + description: "Artifact retention in days (mode=save)" + required: false + default: '3' + token: + description: "Token for the GitHub API (mode=restore); defaults to the job's GITHUB_TOKEN" + required: false + default: '' + +outputs: + key: + description: "Echoes the input 'key', so a later save can reuse it without repeating the expression" + value: ${{ inputs.key }} + cache-hit: + description: "'true' if an artifact was restored (mode=restore)" + value: ${{ steps.restore.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Check mode + shell: bash + env: + MODE: ${{ inputs.mode }} + run: | + case "$MODE" in + restore|save) ;; + *) echo "::error::artifact-cache: 'mode' must be 'restore' or 'save' (got '$MODE')"; exit 1 ;; + esac + + # Scope the base key: -pr- for pull requests (with the target branch + # as fallback), else -branch-. Refs come via env, not ${{ }} + # interpolation, so a branch name with shell metacharacters can't inject. + - name: Compute artifact name + id: name + shell: bash + env: + BASE_KEY: ${{ inputs.key }} + EVENT_NAME: ${{ github.event_name }} + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + sanitize() { printf '%s' "$1" | tr '/' '-'; } # artifact names cannot contain '/' + if [ "$EVENT_NAME" = pull_request ]; then + { + echo "primary=${BASE_KEY}-pr-${PR_NUMBER}" + echo "primary-branch=" + echo "fallback=${BASE_KEY}-branch-$(sanitize "$BASE_REF")" + echo "fallback-branch=$BASE_REF" + } >> "$GITHUB_OUTPUT" + else + { + echo "primary=${BASE_KEY}-branch-$(sanitize "$REF_NAME")" + echo "primary-branch=$REF_NAME" + echo "fallback=" + echo "fallback-branch=" + } >> "$GITHUB_OUTPUT" + fi + + - name: Restore artifact cache + id: restore + if: ${{ inputs.mode == 'restore' }} + continue-on-error: true # a cold start is fine; a restore failure must not fail the caller + shell: bash + env: + GH_TOKEN: ${{ inputs.token || github.token }} + CACHE_PATH: ${{ inputs.path }} + PRIMARY: ${{ steps.name.outputs.primary }} + PRIMARY_BRANCH: ${{ steps.name.outputs.primary-branch }} + FALLBACK: ${{ steps.name.outputs.fallback }} + FALLBACK_BRANCH: ${{ steps.name.outputs.fallback-branch }} + run: | + set -euo pipefail + mkdir -p "$CACHE_PATH" + # Newest non-expired artifact named $1 (empty if none). With a non-empty + # $2 (branch), require provenance: a same-repo run (head_repository_id == + # repository_id) whose head branch is $2, so a fork cannot forge it. + newest_run_id() { + want="$1" br="$2" gh api \ + "/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \ + --jq '.artifacts[] + | select(.expired == false and .name == env.want) + | select(env.br == "" + or (.workflow_run.head_branch == env.br + and .workflow_run.head_repository_id == .workflow_run.repository_id)) + | [.created_at, (.workflow_run.id | tostring)] | @tsv' \ + | sort | tail -n1 | cut -f2 + } + restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore + local name="$1" br="$2" rid tarball + [ -n "$name" ] || return 1 + rid="$(newest_run_id "$name" "$br")" || return 1 + [ -n "$rid" ] || return 1 + echo "Restoring '$CACHE_PATH' from artifact '$name' (run $rid)" + gh run download "$rid" -R "$GITHUB_REPOSITORY" --name "$name" --dir "$RUNNER_TEMP/artifact-cache-dl" || return 1 + # Each artifact holds a single tarball; match by extension so the inner + # filename is not part of the contract. + tarball="$(find "$RUNNER_TEMP/artifact-cache-dl" -name '*.tar.zst' -print -quit)" + [ -n "$tarball" ] || return 1 + tar -I zstd -x -f "$tarball" -C "$CACHE_PATH" + } + if restore "$PRIMARY" "$PRIMARY_BRANCH"; then + echo "Restored from primary key" + echo "cache-hit=true" >> "$GITHUB_OUTPUT" + elif restore "$FALLBACK" "$FALLBACK_BRANCH"; then + echo "Restored from fallback key" + echo "cache-hit=true" >> "$GITHUB_OUTPUT" + else + echo "No matching artifact found; starting cold" + echo "cache-hit=false" >> "$GITHUB_OUTPUT" + fi + + - name: Pack artifact cache + id: pack + if: ${{ inputs.mode == 'save' }} + shell: bash + env: + CACHE_PATH: ${{ inputs.path }} + run: | + set -euo pipefail + packdir="$(mktemp -d)" # unique dir so concurrent saves cannot collide + tar -I 'zstd -T0' -cf "$packdir/cache.tar.zst" -C "$CACHE_PATH" . + echo "tarball=$packdir/cache.tar.zst" >> "$GITHUB_OUTPUT" + + - name: Upload artifact cache + if: ${{ inputs.mode == 'save' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ steps.name.outputs.primary }} + path: ${{ steps.pack.outputs.tarball }} + retention-days: ${{ inputs.retention-days }} + overwrite: true + compression-level: 0 # tarball is already zstd-compressed diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index dd8ed87f7..7a5047c3e 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -42,7 +42,6 @@ env: CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - GH_TOKEN: ${{ github.token }} defaults: run: @@ -69,72 +68,13 @@ jobs: ls -lsha # Test-job ccache is stored as an artifact, not actions/cache - # - pull_request: restore this PR's own previous run; if none, fall back to the PR's target branch. - # - branch push: restore that branch's own cache - # Artifact names are a global, unauthenticated namespace, so a PR could - # upload a "-branch-" artifact impersonating a branch. Branch-scoped - # restores are therefore provenance-checked below (must come from a same-repo - # push to that branch); the pr- scope is not (a PR can only affect its own - # unmerged cache). - - name: Compute ccache artifact name - # Pass github refs via env, not ${{ }} interpolation, so a branch name - # containing shell metacharacters can't break or inject into the script. - env: - BASE_REF: ${{ github.event.pull_request.base.ref }} - REF_NAME: ${{ github.ref_name }} - run: | - base="ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }}" - sanitize() { printf '%s' "$1" | tr '/' '_'; } # artifact names cannot contain '/' - if [ "${{ github.event_name }}" = pull_request ]; then - echo "CCACHE_ARTIFACT=${base}-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_ENV" - echo "CCACHE_ARTIFACT_BRANCH=" >> "$GITHUB_ENV" - echo "CCACHE_FALLBACK=${base}-branch-$(sanitize "$BASE_REF")" >> "$GITHUB_ENV" - echo "CCACHE_FALLBACK_BRANCH=${BASE_REF}" >> "$GITHUB_ENV" - else - echo "CCACHE_ARTIFACT=${base}-branch-$(sanitize "$REF_NAME")" >> "$GITHUB_ENV" - echo "CCACHE_ARTIFACT_BRANCH=${REF_NAME}" >> "$GITHUB_ENV" - fi - - - name: Restore ccache from artifact - continue-on-error: true # a cold start is fine; never fail the job here - run: | - set -euo pipefail - mkdir -p "$CCACHE_DIR" - # Return newest non-expired artifact named $1 (empty if none). When - # $2 (branch) is non-empty, require provenance: the artifact must come from - # a same-repo run (head_repository_id == repository_id) whose head branch - # is $2, so a fork PR cannot forge a branch-scoped artifact. - newest_run_id() { - want="$1" br="$2" gh api \ - "/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \ - --jq '.artifacts[] - | select(.expired == false and .name == env.want) - | select(env.br == "" - or (.workflow_run.head_branch == env.br - and .workflow_run.head_repository_id == .workflow_run.repository_id)) - | [.created_at, (.workflow_run.id | tostring)] | @tsv' \ - | sort | tail -n1 | cut -f2 - } - restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore - local name="$1" br="$2" rid tarball - [ -n "$name" ] || return 1 - rid="$(newest_run_id "$name" "$br")" || return 1 - [ -n "$rid" ] || return 1 - echo "Restoring ccache from '$name' (run $rid)" - gh run download "$rid" --name "$name" --dir "$RUNNER_TEMP/ccache-dl" || return 1 - tarball="$(find "$RUNNER_TEMP/ccache-dl" -name ccache.tar.zst -print -quit)" - [ -n "$tarball" ] || return 1 - tar -I zstd -x -f "$tarball" -C "$CCACHE_DIR" - } - if restore "${CCACHE_ARTIFACT:-}" "${CCACHE_ARTIFACT_BRANCH:-}"; then - echo "Restored primary ccache" - exit 0 - fi - if restore "${CCACHE_FALLBACK:-}" "${CCACHE_FALLBACK_BRANCH:-}"; then - echo "Restored fallback ccache" - exit 0 - fi - echo "No ccache artifact found; starting cold" + - name: Restore ccache + id: ccache + uses: ./repo/.github/actions/artifact-cache + with: + mode: restore + path: ${{ env.CCACHE_DIR }} + key: ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }} - name: Install test dependencies run: | @@ -168,19 +108,15 @@ jobs: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} - - name: Pack ccache - if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} - run: tar -I 'zstd -T0' -cf "$GITHUB_WORKSPACE/ccache.tar.zst" -C "$CCACHE_DIR" . - - - name: Save ccache artifact - if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + - name: Save ccache + if: ${{ !cancelled() }} + uses: ./repo/.github/actions/artifact-cache with: - name: ${{ env.CCACHE_ARTIFACT }} - path: ${{ github.workspace }}/ccache.tar.zst - retention-days: 3 - overwrite: true - compression-level: 0 + mode: save + path: ${{ env.CCACHE_DIR }} + key: ${{ steps.ccache.outputs.key }} + # Keep master's cache around longer; PR/branch caches churn faster + retention-days: ${{ github.ref == 'refs/heads/master' && 7 || 3 }} - name: Fail job if a test failed if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }} diff --git a/ci/ci-test.bash b/ci/ci-test.bash index 52183f7bf..9d0b69517 100755 --- a/ci/ci-test.bash +++ b/ci/ci-test.bash @@ -8,7 +8,7 @@ # Executed in the 'test' stage. ################################################################################ -# Destructive to the checkout (runs 'find . -delete'); never run locally +# Destructive to the checkout (wipes most of it); never run locally if [ "$GITHUB_ACTIONS" != "true" ]; then echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 exit 1 @@ -66,8 +66,8 @@ if [ -n "$OPT_RELOC" ]; then mv test_regress "$TEST_REGRESS" NODIST="$OPT_RELOC/nodist" mv nodist "$NODIST" - # Delete everything else - find . -delete + # Delete everything else, but keep the CI infrastructure + find . -mindepth 1 -maxdepth 1 ! -name .github ! -name ci -exec rm -rf {} + ls -la . fi From 6baea8d8462246fcbd20469ef2b7a74c224e75af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:50:16 -0400 Subject: [PATCH 19/24] CI: Bump the everything group with 5 updates (#7884) --- .github/workflows/docker.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f41c513a5..a059439e2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 with: images: | ${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }} @@ -64,21 +64,21 @@ jobs: type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }} - name: Set up QEMU - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 with: buildkitd-flags: --debug - name: Login to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 with: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build and Push to Docker - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' with: context: ${{ env.build_context }} From 56b0b679c6418127f9b191068fc257100aaebace Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 17:05:14 +0100 Subject: [PATCH 20/24] CI: Disable ccache on coverage jobs (#7887) gcc --coverage is unsupported by ccache and results in a bypass. Don't save unnecessary caches. --- .github/workflows/reusable-build.yml | 10 ++++++---- .github/workflows/reusable-test.yml | 10 +++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 06b668c2d..884d1a1f9 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -71,10 +71,12 @@ jobs: # behind 'verilator --version' fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }} - # ccache is unreliable on the macOS runners; disable it there - - name: Disable ccache on macOS - if: ${{ startsWith(inputs.runs-on, 'macos') }} - run: echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + - name: Configure ccache + run: | + # ccache is unreliable on macOS, and does nothing with 'gcc --coverage' + if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then + echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + fi - name: Cache $CCACHE_DIR if: ${{ env.CCACHE_DISABLE != '1' }} diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 7a5047c3e..bd5a76c7c 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -67,9 +67,17 @@ jobs: tar --zstd -x -f ${{ inputs.archive }} ls -lsha + - name: Configure ccache + run: | + # ccache is unreliable on macOS, and does nothing with 'gcc --coverage' + if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then + echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + fi + # Test-job ccache is stored as an artifact, not actions/cache - name: Restore ccache id: ccache + if: ${{ env.CCACHE_DISABLE != '1' }} uses: ./repo/.github/actions/artifact-cache with: mode: restore @@ -109,7 +117,7 @@ jobs: name: code-coverage-${{ inputs.suite }} - name: Save ccache - if: ${{ !cancelled() }} + if: ${{ env.CCACHE_DISABLE != '1' && !cancelled() }} uses: ./repo/.github/actions/artifact-cache with: mode: save From 39b646349f173a05222c3cf9b35262e453cb400c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 6 Jul 2026 18:02:37 +0100 Subject: [PATCH 21/24] Add --enable-light-debug configure option (#7886) Change default debug build to use -O0 instead of -Og as the latter is still not great debugging experience. Use -Og -gz, plus the new -ggdb1, which together significantly cut down debug info size, to save CI cache space, while still enabling -gdbbt tests to work. --- ci/ci-build.bash | 2 +- configure.ac | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/ci/ci-build.bash b/ci/ci-build.bash index e9c2b2a20..64f037b33 100755 --- a/ci/ci-build.bash +++ b/ci/ci-build.bash @@ -60,7 +60,7 @@ esac ################################################################################ # Configure -CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests" +CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests --enable-light-debug" if [ "$OPT_CCWARN" = 1 ]; then CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn" fi diff --git a/configure.ac b/configure.ac index 6376593ad..893bf91d0 100644 --- a/configure.ac +++ b/configure.ac @@ -129,6 +129,21 @@ AC_ARG_ENABLE([dev-gcov], AC_SUBST(CFG_WITH_DEV_GCOV) AC_MSG_RESULT($CFG_WITH_DEV_GCOV) +# Flag to reduce the debug info in the debug executable to minimize its size +AC_MSG_CHECKING(whether to reduce debug info in the debug executable) +AC_ARG_ENABLE([light-debug], + [AS_HELP_STRING([--enable-light-debug], + [Reduce the amount of debug information in the debug + Verilator executable to minimize its size. Enable + slight optimization. Enough for backtraces only.])], + [case "${enableval}" in + yes) CFG_WITH_LIGHT_DEBUG=yes ;; + no) CFG_WITH_LIGHT_DEBUG=no ;; + *) AC_MSG_ERROR([bad value '${enableval}' for --enable-light-debug]) ;; + esac], + CFG_WITH_LIGHT_DEBUG=no) +AC_MSG_RESULT($CFG_WITH_LIGHT_DEBUG) + # Special Substitutions - CFG_WITH_DEFENV AC_MSG_CHECKING(whether to use hardcoded paths) AC_ARG_ENABLE([defenv], @@ -518,15 +533,27 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PARSER,-Wno-unused) AC_SUBST(CFG_CXXFLAGS_PARSER) # Flags for compiling the debug version of Verilator (in addition to above CFG_CXXFLAGS_SRC) -if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og) +if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then + # Slight optimization and minimal compressed debug info. This is enough for + # --gdb/--gdbbt backtraces, but omits the bulk of the debug info, which + # significantly reduces object file sizes. For CI or release builds. + if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og) + fi + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb1) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-fdebug-info-for-profiling) # For fully-qualified names with clang + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz) +else + # Full debug: no optimization, full debug info, for development. + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-O0) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb) fi -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb) -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz) AC_SUBST(CFG_CXXFLAGS_DBG) # Flags for linking the debug version of Verilator (in addition to above CFG_LDFLAGS_SRC) -_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz) +if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then + _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz) +fi AC_SUBST(CFG_LDFLAGS_DBG) # Flags for compiling the optimized version of Verilator (in addition to above CFG_CXXFLAGS_SRC) From 8ed0acf1ece4fc76b317540efaadbbcd2a9e8070 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 6 Jul 2026 19:12:01 +0200 Subject: [PATCH 22/24] Support a sequence used as an event control (#7797) (#7846) --- src/V3Assert.cpp | 75 ++++++++++++-- src/V3Assert.h | 1 + src/V3AssertNfa.cpp | 45 ++++++--- src/V3AssertPre.cpp | 4 +- src/V3AstNodeStmt.h | 4 + src/V3AstNodes.cpp | 2 + src/Verilator.cpp | 1 + test_regress/t/t_assert_seq_event.py | 18 ++++ test_regress/t/t_assert_seq_event.v | 97 +++++++++++++++++++ test_regress/t/t_assert_seq_event_bad.out | 6 ++ test_regress/t/t_assert_seq_event_bad.py | 16 +++ test_regress/t/t_assert_seq_event_bad.v | 23 +++++ test_regress/t/t_assert_seq_event_ctl.py | 18 ++++ test_regress/t/t_assert_seq_event_ctl.v | 73 ++++++++++++++ test_regress/t/t_assert_seq_event_unsup.out | 13 +++ test_regress/t/t_assert_seq_event_unsup.py | 16 +++ test_regress/t/t_assert_seq_event_unsup.v | 42 ++++++++ test_regress/t/t_debug_emitv.out | 15 +++ test_regress/t/t_debug_emitv.v | 11 +++ test_regress/t/t_sequence_ref_bad.out | 7 ++ ...nce_ref_unsup.py => t_sequence_ref_bad.py} | 0 ...uence_ref_unsup.v => t_sequence_ref_bad.v} | 0 test_regress/t/t_sequence_ref_unsup.out | 6 -- 23 files changed, 465 insertions(+), 28 deletions(-) create mode 100755 test_regress/t/t_assert_seq_event.py create mode 100644 test_regress/t/t_assert_seq_event.v create mode 100644 test_regress/t/t_assert_seq_event_bad.out create mode 100755 test_regress/t/t_assert_seq_event_bad.py create mode 100644 test_regress/t/t_assert_seq_event_bad.v create mode 100755 test_regress/t/t_assert_seq_event_ctl.py create mode 100644 test_regress/t/t_assert_seq_event_ctl.v create mode 100644 test_regress/t/t_assert_seq_event_unsup.out create mode 100755 test_regress/t/t_assert_seq_event_unsup.py create mode 100644 test_regress/t/t_assert_seq_event_unsup.v create mode 100644 test_regress/t/t_sequence_ref_bad.out rename test_regress/t/{t_sequence_ref_unsup.py => t_sequence_ref_bad.py} (100%) rename test_regress/t/{t_sequence_ref_unsup.v => t_sequence_ref_bad.v} (100%) delete mode 100644 test_regress/t/t_sequence_ref_unsup.out diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index e7724cfda..cbf462260 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -90,6 +90,58 @@ public: explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } }; +// Lower a sequence used as an event control ('@seq', IEEE 1800-2023 9.4.2.4) into a +// synthesized event fired by an internal 'cover sequence' on each end-of-match +class SeqEventLowerVisitor final : public VNVisitor { + // STATE + AstNodeModule* m_modp = nullptr; // Current module + V3UniqueNames m_names{"__VseqEvent"}; // Synthesized event names + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + m_modp = nodep; + iterateChildren(nodep); + } + void visit(AstSenItem* nodep) override { + AstFuncRef* const funcrefp = VN_CAST(nodep->sensp(), FuncRef); + if (funcrefp && VN_IS(funcrefp->taskp(), Sequence)) { + FileLine* const flp = nodep->fileline(); + AstVar* const eventp = new AstVar{flp, VVarType::MODULETEMP, m_names.get(nodep), + m_modp->findBasicDType(VBasicDTypeKwd::EVENT)}; + eventp->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(eventp); + v3Global.setHasEvents(); + funcrefp->unlinkFrBack(); + nodep->sensp(new AstVarRef{flp, eventp, VAccess::READ}); + const bool automaticActual = funcrefp->exists([](const AstNodeVarRef* refp) { + return refp->varp() && refp->varp()->lifetime().isAutomatic(); + }); + if (automaticActual) { + nodep->v3error("Arguments to a sequence used as an event control must be" + " static (IEEE 1800-2023 9.4.2.4)"); + VN_AS(funcrefp->taskp(), Sequence)->isReferenced(false); + VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp); + return; + } + AstFireEvent* const firep + = new AstFireEvent{flp, new AstVarRef{flp, eventp, VAccess::WRITE}, false}; + AstCover* const coverp + = new AstCover{flp, new AstPropSpec{flp, nullptr, nullptr, funcrefp}, firep, + VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + coverp->isSeqEvent(true); + m_modp->addStmtsp(coverp); + return; + } + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit SeqEventLowerVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + } // namespace void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { @@ -97,6 +149,11 @@ void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { { DefaultDisablePropagateVisitor{nodep}; } } +void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) { + { SeqEventLowerVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3); +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, @@ -546,14 +603,19 @@ class AssertVisitor final : public VNVisitor { bool selfDestruct = false; bool passspGated = false; - if (const AstCover* const snodep = VN_CAST(nodep, Cover)) { + const AstCover* const coverp = VN_CAST(nodep, Cover); + // A sequence event control is not an assertion directive; no assertion control + const bool seqEvent = coverp && coverp->isSeqEvent(); + if (coverp) { ++m_statCover; - if (!v3Global.opt.coverageUser()) { + if (seqEvent) { + // Keep the event-fire action, with no coverage bucket + } else if (!v3Global.opt.coverageUser()) { selfDestruct = true; } else { // V3Coverage assigned us a bucket to increment. - AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc); - UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion"); + AstCoverInc* const covincp = VN_AS(coverp->coverincsp(), CoverInc); + UASSERT_OBJ(covincp, coverp, "Missing AstCoverInc under assertion"); covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace if (message != "") covincp->declp()->comment(message); if (passsp) { @@ -597,7 +659,8 @@ class AssertVisitor final : public VNVisitor { FileLine* const flp = nodep->fileline(); bool passspAlreadyGated = false; if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1(); - if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)) { + if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr) + && !seqEvent) { passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), /*vacuous=*/false); } @@ -607,7 +670,7 @@ class AssertVisitor final : public VNVisitor { AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp); if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp}; // Add assertOn check last, for better combining - bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType()); + if (!seqEvent) bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType()); if (sentreep) bodysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, sentreep, bodysp}; if (passsp && !passsp->backp()) VL_DO_DANGLING(pushDeletep(passsp), passsp); diff --git a/src/V3Assert.h b/src/V3Assert.h index f144688b6..3bda2528e 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -27,6 +27,7 @@ class V3AssertCommon final { public: static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; + static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED; }; class V3Assert final { diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 6a35273a5..f01e0da62 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -287,6 +287,8 @@ class SvaNfaBuilder final { // not just the first. Builder builds parallel-branch (no first-match-wins) // topology when true. Default false preserves cover_property semantics. bool m_isCoverSeq = false; + // Unsupported endpoint topology must reject, not ignore, or the wait hangs + bool m_isSeqEvent = false; struct RangeDelayRejectInfo final { SvaStateVertex* startp = nullptr; @@ -294,6 +296,15 @@ class SvaNfaBuilder final { int rhsLen = 0; }; + void warnEndpointUnsupported(FileLine* flp, const string& what) const { + if (m_isSeqEvent) { + flp->v3warn(E_UNSUPPORTED, + "Unsupported: sequence used as an event control with " << what); + } else { + flp->v3warn(COVERIGN, "Ignoring unsupported: cover sequence with " << what); + } + } + AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; // AND all throughout conditions (supports nesting) @@ -574,8 +585,7 @@ class SvaNfaBuilder final { // overlapping ends and the nested-sequence merge collapses them, so // reject those for a cover sequence rather than under-count. if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with this ranged cycle delay"); + warnEndpointUnsupported(flp, "this ranged cycle delay"); outErrorEmitted = true; return false; } @@ -881,8 +891,7 @@ class SvaNfaBuilder final { // terminal, so a cover sequence would under-count. Reject the ranged form // (the single-count b[->N] has one end and is enumerated correctly). if (m_isCoverSeq && hasMax && maxN > minN) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with a ranged goto repetition"); + warnEndpointUnsupported(flp, "a ranged goto repetition"); return BuildResult::failWithError(); } @@ -946,8 +955,7 @@ class SvaNfaBuilder final { // than under-count. Plain boolean disjunction has one end per cycle and // is handled by the OR-fold. if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + warnEndpointUnsupported(flp, "a sequence operand of 'or'"); return BuildResult::failWithError(); } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); @@ -1442,11 +1450,12 @@ class SvaNfaBuilder final { public: SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, - bool isCoverSeq = false) + bool isCoverSeq = false, bool isSeqEvent = false) : m_graph{graph} , m_modp{modp} , m_propTempNames{propTempNames} - , m_isCoverSeq{isCoverSeq} {} + , m_isCoverSeq{isCoverSeq} + , m_isSeqEvent{isSeqEvent} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -2876,11 +2885,17 @@ class AssertNfaVisitor final : public VNVisitor { bool senTreeOwned = false; // True if we created senTreep locally AstPropSpec* const propSpecp = VN_CAST(assertp->propp(), PropSpec); UASSERT_OBJ(propSpecp, assertp, "Concurrent assertion must have PropSpec"); + AstCover* const coverp = VN_CAST(assertp, Cover); + const bool isCover = coverp != nullptr; + const bool isCoverSeq = coverp && coverp->isCoverSeq(); + // A sequence event control is not an assertion directive; no default + // disable iff, no assertion control + const bool isSeqEvent = coverp && coverp->isSeqEvent(); // Inherit module defaults (IEEE 14.12, 16.15) when assertion has none. if (!propSpecp->sensesp() && m_defaultClockingp) { propSpecp->sensesp(m_defaultClockingp->sensesp()->cloneTree(true)); } - if (!propSpecp->disablep() && m_defaultDisablep) { + if (!propSpecp->disablep() && m_defaultDisablep && !isSeqEvent) { propSpecp->disablep(m_defaultDisablep->condp()->cloneTreePure(true)); } if (!senTreep && propSpecp->sensesp()) { @@ -2892,12 +2907,9 @@ class AssertNfaVisitor final : public VNVisitor { if (!senTreep) return; FileLine* const flp = assertp->fileline(); - const bool isCover = VN_IS(assertp, Cover); - AstCover* const coverp = VN_CAST(assertp, Cover); - const bool isCoverSeq = coverp && coverp->isCoverSeq(); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq, isSeqEvent}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2939,13 +2951,16 @@ class AssertNfaVisitor final : public VNVisitor { std::vector perMidSrcs; AstNodeExpr* const alwaysTriggerp - = assertOnCond(flp, assertp->userType(), assertp->directive()); + = isSeqEvent ? new AstConst{flp, AstConst::BitTrue{}} + : assertOnCond(flp, assertp->userType(), assertp->directive()); AstNodeExpr* const outputExprp = m_loweringp->lower( flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr, - assertp->userType(), assertp->directive()); + isSeqEvent ? VAssertType{VAssertType::INTERNAL} : assertp->userType(), + isSeqEvent ? VAssertDirectiveType{VAssertDirectiveType::INTERNAL} + : assertp->directive()); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index f45df83f3..d9332ff59 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -1466,7 +1466,9 @@ private: iterateAndNextNull(nodep->sensesp()); if (m_senip && m_senip != nodep->sensesp()) nodep->v3warn(E_UNSUPPORTED, "Unsupported: Only one PSL clock allowed per assertion"); - if (!nodep->disablep() && m_defaultDisablep) { + const AstCover* const coverp = VN_CAST(nodep->backp(), Cover); + const bool seqEvent = coverp && coverp->isSeqEvent(); + if (!nodep->disablep() && m_defaultDisablep && !seqEvent) { nodep->disablep(m_defaultDisablep->condp()->cloneTreePure(true)); } m_disablep = nodep->disablep(); diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 25de3ad08..fbbceaec7 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1612,6 +1612,8 @@ class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per // end-of-match, not per property success + bool m_isSeqEvent = false; // Synthesized for a sequence used as an event control + // (IEEE 1800-2023 9.4.2.4) public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, @@ -1622,6 +1624,8 @@ public: void dumpJson(std::ostream& str) const override; bool isCoverSeq() const { return m_isCoverSeq; } void isCoverSeq(bool flag) { m_isCoverSeq = flag; } + bool isSeqEvent() const { return m_isSeqEvent; } + void isSeqEvent(bool flag) { m_isSeqEvent = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index aba8a9d0f..26126e5fd 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2241,9 +2241,11 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { void AstCover::dump(std::ostream& str) const { this->AstNodeCoverOrAssert::dump(str); if (isCoverSeq()) str << " [COVERSEQ]"; + if (isSeqEvent()) str << " [SEQEVENT]"; } void AstCover::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isCoverSeq); + dumpJsonBoolFuncIf(str, isSeqEvent); this->AstNodeCoverOrAssert::dumpJson(str); } void AstClocking::dump(std::ostream& str) const { diff --git a/src/Verilator.cpp b/src/Verilator.cpp index b1f0a9af0..b694691dd 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -261,6 +261,7 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms V3AssertCommon::collectDefaultDisable(v3Global.rootp()); + V3AssertCommon::lowerSequenceEvents(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. diff --git a/test_regress/t/t_assert_seq_event.py b/test_regress/t/t_assert_seq_event.py new file mode 100755 index 000000000..e8cdbc78d --- /dev/null +++ b/test_regress/t/t_assert_seq_event.py @@ -0,0 +1,18 @@ +#!/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('vlt') + +test.compile(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_event.v b/test_regress/t/t_assert_seq_event.v new file mode 100644 index 000000000..d82078cff --- /dev/null +++ b/test_regress/t/t_assert_seq_event.v @@ -0,0 +1,97 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b, c; + bit a1, a2, a3, b1; + int cyc; + int seq_hits, seq_hits2, ref_hits, one_hits, dc_hits; + int rng_hits, rng_ref; + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence seq; + @(posedge clk) a ##1 b ##1 c; + endsequence + + sequence seq_one; + @(posedge clk) a; + endsequence + + sequence seq_rng; + @(posedge clk) a ##[1:3] b; + endsequence + // verilog_format: on + + // seq_dc inherits the default clocking; counts must match seq + default clocking @(posedge clk); + endclocking + + sequence seq_dc; + a ##1 b ##1 c; + endsequence + + // ref_hits and rng_ref are independent shift-register oracles; + // simultaneous end points resume a blocked waiter once + initial forever begin + @seq; + seq_hits = seq_hits + 1; + end + initial forever begin + @seq; + seq_hits2 = seq_hits2 + 1; + end + initial forever begin + @seq_one; + one_hits = one_hits + 1; + end + initial forever begin + @seq_dc; + dc_hits = dc_hits + 1; + end + initial forever begin + @seq_rng; + rng_hits = rng_hits + 1; + end + + always @(posedge clk) begin + if (a2 && b1 && c) ref_hits = ref_hits + 1; + if (b && (a1 || a2 || a3)) rng_ref = rng_ref + 1; + a3 <= a2; + a2 <= a1; + a1 <= a; + b1 <= b; + end + + // a/b/c bit spacing exceeds the ##2 window to decorrelate the LFSR taps + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[4]; + c <= crc[8]; + if (cyc == 60) $finish; + end + + // Counts read in final (Postponed) to avoid same-timestep races. + final begin + `checkd(seq_hits, 14); + `checkd(seq_hits2, 14); + `checkd(ref_hits, 14); + `checkd(one_hits, 30); + `checkd(dc_hits, 14); + `checkd(rng_hits, 26); + `checkd(rng_ref, 26); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_event_bad.out b/test_regress/t/t_assert_seq_event_bad.out new file mode 100644 index 000000000..7e24b4b9f --- /dev/null +++ b/test_regress/t/t_assert_seq_event_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_assert_seq_event_bad.v:19:7: Arguments to a sequence used as an event control must be static (IEEE 1800-2023 9.4.2.4) + : ... note: In instance 't' + 19 | @(s_arg(x)); + | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assert_seq_event_bad.py b/test_regress/t/t_assert_seq_event_bad.py new file mode 100755 index 000000000..f051d14b9 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_bad.py @@ -0,0 +1,16 @@ +#!/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('vlt') + +test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_seq_event_bad.v b/test_regress/t/t_assert_seq_event_bad.v new file mode 100644 index 000000000..d6621aa9d --- /dev/null +++ b/test_regress/t/t_assert_seq_event_bad.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + // verilog_format: off + sequence s_arg(x); + @(posedge clk) x; + endsequence + // verilog_format: on + + task automatic f; + bit x = 1; + @(s_arg(x)); + endtask + + initial f(); +endmodule diff --git a/test_regress/t/t_assert_seq_event_ctl.py b/test_regress/t/t_assert_seq_event_ctl.py new file mode 100755 index 000000000..e8cdbc78d --- /dev/null +++ b/test_regress/t/t_assert_seq_event_ctl.py @@ -0,0 +1,18 @@ +#!/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('vlt') + +test.compile(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_event_ctl.v b/test_regress/t/t_assert_seq_event_ctl.v new file mode 100644 index 000000000..ce9931e4b --- /dev/null +++ b/test_regress/t/t_assert_seq_event_ctl.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b, rst; + bit a1; + int cyc; + int hits, ref_hits, one_hits, one_ref; + + // Neither the default disable iff nor $assertoff may suppress a sequence event control + default disable iff (rst); + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence seq; + @(posedge clk) a ##1 b; + endsequence + + sequence seq_one; + @(posedge clk) 1; + endsequence + // verilog_format: on + + initial begin + $assertoff; + #300 $assertkill; + end + + initial forever begin + @seq; + hits = hits + 1; + end + initial forever begin + @seq_one; + one_hits = one_hits + 1; + end + + always @(posedge clk) begin + if (a1 && b) ref_hits = ref_hits + 1; + one_ref = one_ref + 1; + a1 <= a; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[4]; + rst <= crc[2]; + end + + always @(negedge clk) begin + if (cyc == 60) $finish; + end + + final begin + `checkd(hits, ref_hits); + `checkd(one_hits, one_ref); + `checkd(hits, 19); + `checkd(one_hits, 60); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_event_unsup.out b/test_regress/t/t_assert_seq_event_unsup.out new file mode 100644 index 000000000..61d38b9f7 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.out @@ -0,0 +1,13 @@ +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:18:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 18 | @(g) a ##1 b; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:28:30: Unsupported: sequence used as an event control with a sequence operand of 'or' + 28 | @(posedge clk) (a ##1 b) or (a ##2 b); + | ^~ +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:21:12: Unsupported: sequence referenced outside assertion property + : ... note: In instance 't' + 21 | sequence s_ref; + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_seq_event_unsup.py b/test_regress/t/t_assert_seq_event_unsup.py new file mode 100755 index 000000000..f051d14b9 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.py @@ -0,0 +1,16 @@ +#!/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('vlt') + +test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_seq_event_unsup.v b/test_regress/t/t_assert_seq_event_unsup.v new file mode 100644 index 000000000..eda3b0772 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + bit a, b; + logic g = 0; + + default clocking @(posedge clk); + endclocking + + // verilog_format: off + sequence s_nonedge; + @(g) a ##1 b; + endsequence + + sequence s_ref; + @(posedge clk) a; + endsequence + + // Legal but its endpoint topology is not buildable, so the wait could never + // resume; rejected rather than silently ignored. + sequence s_or; + @(posedge clk) (a ##1 b) or (a ##2 b); + endsequence + // verilog_format: on + + // Legal: p is never asserted, so s_ref stays referenced outside any + // assertion property, which is not yet supported. + property p; + s_ref; + endproperty + + initial begin + @s_nonedge; + @s_or; + end +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 8c0ad2245..5fcf5c7d7 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -1318,6 +1318,21 @@ module Vt_debug_emitv_sub; endfunction real r; endmodule +module Vt_debug_emitv_seq_event; + input logic clk; + bit a; + bit b; + bit c; + sequence sq; + @(posedge clk) a##1 b##1 c + endsequence + initial begin + begin + + ???? // EVENTCONTROL + @( sq())end + end +endmodule package Vt_debug_emitv_p; logic pkgvar; endpackage diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 813d02ea9..7e474c305 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -138,6 +138,7 @@ module t (/*AUTOARG*/ endfunction sub sub(.*); + seq_event seq_event(.*); initial begin int other; @@ -443,6 +444,16 @@ module sub(input logic clk); real r; endmodule +module seq_event(input logic clk); + bit a, b, c; + sequence sq; + @(posedge clk) a ##1 b ##1 c; + endsequence + initial begin + @sq; + end +endmodule + package p; logic pkgvar; endpackage diff --git a/test_regress/t/t_sequence_ref_bad.out b/test_regress/t/t_sequence_ref_bad.out new file mode 100644 index 000000000..afe2fe2f7 --- /dev/null +++ b/test_regress/t/t_sequence_ref_bad.out @@ -0,0 +1,7 @@ +%Error: t/t_sequence_ref_bad.v:14:6: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 14 | @s_one; + | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_ref_unsup.py b/test_regress/t/t_sequence_ref_bad.py similarity index 100% rename from test_regress/t/t_sequence_ref_unsup.py rename to test_regress/t/t_sequence_ref_bad.py diff --git a/test_regress/t/t_sequence_ref_unsup.v b/test_regress/t/t_sequence_ref_bad.v similarity index 100% rename from test_regress/t/t_sequence_ref_unsup.v rename to test_regress/t/t_sequence_ref_bad.v diff --git a/test_regress/t/t_sequence_ref_unsup.out b/test_regress/t/t_sequence_ref_unsup.out deleted file mode 100644 index 41fb50aad..000000000 --- a/test_regress/t/t_sequence_ref_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_sequence_ref_unsup.v:9:12: Unsupported: sequence referenced outside assertion property - : ... note: In instance 't' - 9 | sequence s_one; - | ^~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to From a5f4d40901389d560d1366582670350ffe4e27cd Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Mon, 6 Jul 2026 19:13:11 +0200 Subject: [PATCH 23/24] Fix range delays with parameter bounds (#7882) Signed-off-by: Artur Bieniek --- src/V3AssertNfa.cpp | 4 ++-- src/V3Width.cpp | 11 +++++++++++ test_regress/t/t_property_sexpr_range_delay.v | 7 +++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index f01e0da62..b96696cff 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -472,8 +472,8 @@ class SvaNfaBuilder final { AstVar* tryHoistSampled(AstNodeExpr* exprp, FileLine* flp, int cloneCount) { constexpr int kHoistThreshold = 2; if (cloneCount < kHoistThreshold) return nullptr; - AstVar* const tempVarp = new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp), - m_modp->findBitDType()}; + AstVar* const tempVarp + = new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp), exprp->dtypep()}; m_modp->addStmtsp(tempVarp); AstAssign* const assignp = new AstAssign{flp, new AstVarRef{flp, tempVarp, VAccess::WRITE}, sampled(exprp->cloneTreePure(false))}; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index acc20248d..802fe155d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -731,6 +731,17 @@ class WidthVisitor final : public VNVisitor { iterateCheckBool(nodep, "default disable iff condition", nodep->condp(), BOTH); } void visit(AstDelay* nodep) override { + if (nodep->isCycleDelay() && m_underSExpr) { + // Fold parameterized SVA cycle-delay bounds + userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); + V3Const::constifyParamsNoWarnEdit(nodep->lhsp()); + if (nodep->rhsp() && !nodep->isUnbounded()) { + // Fold parametrized SVA cycle-delay max bound + userIterateAndNext(nodep->rhsp(), WidthVP{SELF, BOTH}.p()); + V3Const::constifyParamsNoWarnEdit(nodep->rhsp()); + } + return; + } if (AstNodeExpr* const fallDelayp = nodep->fallDelay()) { iterateCheckDelay(nodep, "delay", nodep->lhsp(), BOTH); iterateCheckDelay(nodep, "delay", fallDelayp, BOTH); diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index cd3037d03..85ad2dcc8 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -13,6 +13,8 @@ module t ( input clk ); + parameter P = 1; + integer cyc = 0; reg [63:0] crc = '0; reg [63:0] sum = '0; @@ -64,6 +66,11 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[1:3] (a | b | c | d | e)); + // Parameterized range bound + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[P:P+3] (a | b | c | d | e)); + assert property (@(posedge clk) ##[P:P+3] 1); + // ##[2:4] range delay assert property (@(posedge clk) disable iff (cyc < 2) b |-> ##[2:4] (a | b | c | d | e)); From aca58ca90f886c5dc4a07efe6ebd36e06f9c414c Mon Sep 17 00:00:00 2001 From: Igor Zaworski Date: Tue, 7 Jul 2026 13:34:24 +0200 Subject: [PATCH 24/24] Fix scheduling of variables written in non-inlined functions in suspendable processes (#7836) Signed-off-by: Igor Zaworski --- src/V3SchedTiming.cpp | 91 +++++++++++++++++- .../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, 231 insertions(+), 30 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..b39818b36 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 @@ -193,6 +195,34 @@ class AwaitVisitor final : public VNVisitor { std::set m_processDomains; // Sentrees from the current process // Variables written by suspendable processes std::vector m_writtenBySuspendable; + struct CFuncCache final { + std::set m_processDomains; // What shall be added to m_processDomains + std::vector + m_writtenBySuspendable; // What shall be added to m_writtenBySuspendable + 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; + }; + // 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; + // Set containing information whether an AstCFunc was hit (called) recursively + // This is needed to know whether cache is complete. E.g.: + // A->B->C->B + // Callstack: + // visit(A) -> Ok go to successors + // visit(B) -> Ok go to successors + // visit(C) -> Ok go to successors + // visit(B) -> Already visiting B + // visit(C) -> Cache must not be created since it is not complete - A was not visited + // visit(B) -> Cache may be created since all successors have been transitionally visited + // visit(A) -> Cache may be created since all successors have been transitionally visited + std::array, 2> m_hitVisiting; // METHODS // Add arguments to a resume() call based on arguments in the suspending call @@ -267,6 +297,7 @@ 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() @@ -274,6 +305,59 @@ class AwaitVisitor final : public VNVisitor { m_writtenBySuspendable.push_back(nodep->varScopep()); } } + void visit(AstNodeCCall* const nodep) override { + iterateChildren(nodep); + // We need to visit bodies of non-inlined functions + iterate(nodep->funcp()); + } + void visit(AstCFunc* nodep) override { + UASSERT_OBJ(!m_gatherVars || m_inProcess, nodep, + "Variables shall be gathered only inside processes"); + // Cache key does not depends on a m_inProcess variable since it is forced to be true + const size_t cacheKey = static_cast(m_gatherVars); + CFuncCache& value = m_cfuncsCache(nodep)[cacheKey]; + { + VL_RESTORER(m_inProcess); + // Force m_inProcess since visiting a tree with !m_inProcess does not change the + // m_writtenBySuspendable and m_processDomains (so it would be a bit of a dry run). + // However, we must visit children since there might be an AstCAwait which is modified + // in its visit() but since the function does not depend on a state of m_inProcess (nor + // anything that may be affected by changing the state of m_inProcess) we may force the + // variable, visit children and gather cache for m_inProcess && !m_gatherVars + m_inProcess = true; + 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; + iterateChildren(nodep); + m_hitVisiting[cacheKey].erase(nodep); + value.m_state = m_hitVisiting[cacheKey].empty() ? CFuncCache::INITIALIZED + : CFuncCache::UNINITIALIZED; + + // Save a cache + std::swap(m_processDomains, value.m_processDomains); + std::swap(m_writtenBySuspendable, value.m_writtenBySuspendable); + } break; + case CFuncCache::VISITING: + m_hitVisiting[cacheKey].insert(nodep); + return; // Break recursion + case CFuncCache::INITIALIZED: break; + } + } + if (m_inProcess) { + // Add cached values to the visitor state if the m_inProcess was initially true - when + // it is false none of these variable shall be modified + m_writtenBySuspendable.insert(m_writtenBySuspendable.end(), + 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 +373,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..b6c85ee2d --- /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+)', 7) + +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