diff --git a/CMakeLists.txt b/CMakeLists.txt index 614e5cac9..c7fad1684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,23 @@ set(PACKAGE_VERSION ${PROJECT_VERSION}) set(CXX ${CMAKE_CXX_COMPILER}) set(AR ${CMAKE_AR}) +# Detect precompiled header include flag (matches configure.ac logic) +execute_process( + COMMAND ${CMAKE_CXX_COMPILER} --help + OUTPUT_VARIABLE _cxx_help + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) +if(_cxx_help MATCHES "include-pch") + # clang + set(CFG_CXXFLAGS_PCH_I "-include-pch") + set(CFG_GCH_IF_CLANG ".gch") +else() + # GCC + set(CFG_CXXFLAGS_PCH_I "-include") + set(CFG_GCH_IF_CLANG "") +endif() + configure_file(include/verilated_config.h.in include/verilated_config.h @ONLY) configure_file(include/verilated.mk.in include/verilated.mk @ONLY) diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index 23bbd6b8f..07fda8a01 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -129,9 +129,13 @@ verilator_coverage Arguments .. option:: --filter-type Skips records of coverage types that matches with - Possible values are `toggle`, `line`, `branch`, `expr`, `user` and + Possible values are `toggle`, `line`, `branch`, `expr`, `covergroup`, `user` and a wildcard with `\*` or `?`. The default value is `\*`. + The `covergroup` type represents SystemVerilog functional coverage including + covergroups, coverpoints, bins, and cross coverage as defined in IEEE + 1800-2023 Section 19. + .. option:: --help Displays a help summary, the program version, and exits. diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 262a58271..70ded1ce2 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -40,8 +40,10 @@ union, var, void, priority case/if, and unique case/if. It also supports .name and .\* interconnection. -Verilator partially supports concurrent assert and cover statements; see -the enclosed coverage tests for the allowed syntax. +Verilator partially supports concurrent assert and cover statements, as well as +SystemVerilog functional coverage with ``covergroup``, ``coverpoint``, bins, +cross coverage, and transition bins. See :ref:`Functional Coverage` for details. Verilator has limited support for class and related object-oriented constructs. @@ -363,10 +365,15 @@ appropriate width. Assertions ---------- -Verilator is beginning to add support for assertions. Verilator currently -only converts assertions to simple ``if (...) error`` statements, and -coverage statements to increment the line counters described in the -coverage section. +Verilator partially supports assertions and functional coverage. +Verilator currently converts assertions to simple ``if (...) error`` statements, +and simple coverage statements to increment the line counters described in the +:ref:`coverage section`. + +Verilator also partially supports SystemVerilog functional coverage with +``covergroup``, ``coverpoint``, bins, cross coverage, and transition bins. See +:ref:`Functional Coverage` for details on using +covergroups for comprehensive coverage analysis. Verilator does not support SEREs yet. All assertion and coverage statements must be simple expressions that complete in one cycle. diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index fd92bb1ba..d02d08499 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -184,7 +184,8 @@ Verilator supports adding code to the Verilated model to support SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables all forms of coverage: -- :ref:`User Coverage` +- :ref:`Property Coverage` +- :ref:`Covergroup Coverage` - :ref:`Line Coverage` - :ref:`Toggle Coverage` @@ -192,22 +193,113 @@ When a model with coverage is executed, it will create a coverage file for collection and later analysis, see :ref:`Coverage Collection`. -.. _user coverage: +.. _property coverage: -Functional Coverage -------------------- +Property Coverage +----------------- With :vlopt:`--coverage` or :vlopt:`--coverage-user`, Verilator will -translate functional coverage points the user has inserted manually in -SystemVerilog code through into the Verilated model. +translate property coverage points the user has inserted manually in +SystemVerilog code into the Verilated model. -For example, the following SystemVerilog statement will add a coverage -point under the coverage name "DefaultClock": +For simple coverage points, use the ``cover property`` construct: .. code-block:: sv DefaultClock: cover property (@(posedge clk) cyc==3); +This adds a coverage point that tracks whether the condition has been observed. + +.. _covergroup coverage: + +Covergroup Coverage +------------------- + +With :vlopt:`--coverage` or :vlopt:`--coverage-user`, Verilator will +translate covergroup coverage points the user has inserted manually in +SystemVerilog code into the Verilated model. Verilator supports +coverpoints with value and transition bins, and cross points. + +.. code-block:: sv + + module top; + logic [7:0] addr; + logic cmd; + + // Define a covergroup + covergroup cg; + cp_addr: coverpoint addr { + bins low = {[0:127]}; + bins high = {[128:255]}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + endgroup + + // Instantiate the covergroup + cg cg_inst = new; + + always @(posedge clk) begin + // Sample coverage explicitly + cg_inst.sample(); + end + endmodule + + +Supported Features +^^^^^^^^^^^^^^^^^^ +* Coverpoints on integral expressions with value, range, wildcard, and transition bins +* Conditional coverpoint sampling (iff) +* Explicit and clocked sampling, with sample-function parameters +* at_least and auto_bin_max options on covergroups and coverpoints +* Cross points with auto-bins + +Unsupported Features +^^^^^^^^^^^^^^^^^^^^ + +* Coverpoints on real (floating-point) expressions +* Coverpoint bin filtering (with) +* Coverpoint bin conditional sampling (iff) +* Transition bins with repetition operators ([\*N], [->N], [=N]) +* Explicitly-typed coverpoints +* Block-event sampling +* Covergroup inheritance (extends) +* Cross points with user-defined bins + +Functional Coverage Data Format +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Functional coverage data is stored in the coverage data file (typically +:file:`coverage.dat`) using the standard Verilator coverage format. Each +functional coverage bin is recorded as a coverage point with: + +* **Type**: ``funccov`` - identifies the record as functional coverage +* **Page**: ``v_funccov/`` - groups bins by their covergroup +* **Hierarchy**: ``..`` for coverpoints, or + ``..`` for cross coverage +* **Count**: Number of times the bin was hit during simulation + +Example coverage.dat entries: + +.. code-block:: + + C 'tfunccovpagev_funccov/cgftest.vl28hcg.cp_a.low' 150 + C 'tfunccovpagev_funccov/cgftest.vl29hcg.cp_a.high' 75 + C 'tfunccovpagev_funccov/cgftest.vl35hcg.cross_ab.a0_b1' 25 + +To filter functional coverage data, use the :option:`--filter-type` option +with :command:`verilator_coverage`: + +.. code-block:: bash + + # Only process functional coverage + $ verilator_coverage --filter-type funccov --annotate report coverage.dat + + # Exclude functional coverage + $ verilator_coverage --filter-type '!funccov' --annotate report coverage.dat + .. _line coverage: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92bd02219..37668e00c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,7 @@ set(HEADERS V3Control.h V3Coverage.h V3CoverageJoin.h + V3Covergroup.h V3Dead.h V3Delayed.h V3Depth.h @@ -235,6 +236,7 @@ set(COMMON_SOURCES V3Const__gen.cpp V3Coverage.cpp V3CoverageJoin.cpp + V3Covergroup.cpp V3Dead.cpp V3Delayed.cpp V3Depth.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 28f15e698..64f5e7882 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -249,6 +249,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Combine.o \ V3Common.o \ V3Coverage.o \ + V3Covergroup.o \ V3CoverageJoin.o \ V3Dead.o \ V3Delayed.o \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index c8122c2c4..c97f9f45a 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -619,11 +619,236 @@ public: ~ActiveVisitor() override = default; }; +//###################################################################### +// Automatic covergroup sampling visitor +// This runs after ActiveVisitor to add automatic sample() calls for covergroups +// declared with sensitivity events (e.g., covergroup cg @(posedge clk);) + +class CovergroupSamplingVisitor final : public VNVisitor { + // STATE + ActiveNamer m_namer; // Reuse active naming infrastructure + AstScope* m_scopep = nullptr; // Current scope + bool m_inFirstPass = true; // First pass collects CFuncs, second pass adds sampling + std::unordered_map + m_covergroupSampleFuncs; // Class -> sample CFunc + + // Helper to get the clocking event from a covergroup class + AstSenTree* getCovergroupEvent(AstClass* classp) { + // The AstCovergroup (holding the SenTree) was left in membersp by V3Covergroup + for (AstNode* memberp = classp->membersp(); memberp; memberp = memberp->nextp()) { + if (AstCovergroup* const cgp = VN_CAST(memberp, Covergroup)) { + if (cgp->eventp()) return cgp->eventp(); + } + } + return nullptr; + } + + // VISITORS + void visit(AstScope* nodep) override { + m_scopep = nodep; + m_namer.main(nodep); // Initialize active naming for this scope + + // First pass: collect sample CFuncs from covergroup class scopes + if (m_inFirstPass) { + // Check if this is a covergroup class scope (contains sample CFunc) + for (AstNode* itemp = m_scopep->blocksp(); itemp; itemp = itemp->nextp()) { + if (AstCFunc* const cfuncp = VN_CAST(itemp, CFunc)) { + if (cfuncp->name().find("sample") != string::npos) { + // This is a covergroup class scope - find the class and store the CFunc + // The scope name is like "TOP.t__03a__03acg", extract class name + string scopeName = nodep->name(); + size_t dotPos = scopeName.find('.'); + if (dotPos != string::npos) { + string className = scopeName.substr(dotPos + 1); + // Search netlist for the matching covergroup class + for (AstNode* modp = v3Global.rootp()->modulesp(); modp; + modp = modp->nextp()) { + if (AstClass* const classp = VN_CAST(modp, Class)) { + if (classp->isCovergroup() && classp->name() == className) { + m_covergroupSampleFuncs[classp] = cfuncp; + cfuncp->isCovergroupSample(true); + break; + } + } + } + } + break; + } + } + } + } + + iterateChildren(nodep); + m_scopep = nullptr; + } + + void visit(AstVarScope* nodep) override { + // Only process VarScopes in the second pass + if (m_inFirstPass) return; + + // Get the underlying var + AstVar* const varp = nodep->varp(); + if (!varp) return; + + // Check if the variable is of covergroup class type + const AstNodeDType* const dtypep = varp->dtypep(); + if (!dtypep) return; + + const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType); + if (!classRefp) return; + + AstClass* const classp = classRefp->classp(); + if (!classp || !classp->isCovergroup()) return; + + // Check if this covergroup has an automatic sampling event + AstSenTree* const eventp = getCovergroupEvent(classp); + if (!eventp) return; // No automatic sampling for this covergroup + + // Get the sample CFunc - we need to find it in the class scope + // The class scope name is like "TOP.t__03a__03acg" for class "t__03a__03acg" + const string classScopeName = string("TOP.") + classp->name(); + + AstCFunc* sampleCFuncp = nullptr; + // Search through all scopes to find the class scope and its sample CFunc + for (AstNode* scopeNode = m_scopep; scopeNode; scopeNode = scopeNode->backp()) { + if (AstNetlist* netlistp = VN_CAST(scopeNode, Netlist)) { + // Found netlist, search its modules for scopes + for (AstNode* modp = netlistp->modulesp(); modp; modp = modp->nextp()) { + if (AstScope* scopep = VN_CAST(modp, Scope)) { + if (scopep->name() == classScopeName) { + // Found the class scope, now find the sample CFunc + for (AstNode* itemp = scopep->blocksp(); itemp; + itemp = itemp->nextp()) { + if (AstCFunc* cfuncp = VN_CAST(itemp, CFunc)) { + if (cfuncp->name().find("sample") != string::npos) { + sampleCFuncp = cfuncp; + break; + } + } + } + break; + } + } + } + break; + } + } + + if (!sampleCFuncp) { + // Fallback: try the cached version + auto it = m_covergroupSampleFuncs.find(classp); + if (it != m_covergroupSampleFuncs.end()) { sampleCFuncp = it->second; } + } + + if (!sampleCFuncp) { + UINFO(4, "Could not find sample() CFunc for covergroup " << classp->name() << endl); + return; // CFunc not found + } + UASSERT_OBJ(sampleCFuncp, nodep, "Sample CFunc is null for covergroup"); + + // Create a VarRef to the covergroup instance for the method call + FileLine* const fl = nodep->fileline(); + AstVarRef* const varrefp = new AstVarRef{fl, nodep, VAccess::READ}; + + // Create the CMethodCall to sample() + // Note: We don't pass arguments in argsp since vlSymsp is passed via argTypes + AstCMethodCall* const cmethodCallp + = new AstCMethodCall{fl, varrefp, sampleCFuncp, nullptr}; + + // Set dtype to void since sample() doesn't return a value + cmethodCallp->dtypeSetVoid(); + + // Set argTypes to "vlSymsp" so the emit code will pass it automatically + cmethodCallp->argTypes("vlSymsp"); + + // Clone the sensitivity for this active block + // Each VarRef in the sensitivity needs to be updated for the current scope + AstSenTree* senTreep = eventp->cloneTree(false); + + // Fix up VarRefs in the cloned sensitivity - they need varScopep set + senTreep->foreach([this](AstVarRef* refp) { + if (!refp->varScopep() && refp->varp()) { + // Find the VarScope for this Var in the current scope + AstVarScope* vscp = nullptr; + for (AstNode* itemp = m_scopep->varsp(); itemp; itemp = itemp->nextp()) { + if (AstVarScope* const vsp = VN_CAST(itemp, VarScope)) { + if (vsp->varp() == refp->varp()) { + vscp = vsp; + break; + } + } + } + if (vscp) { + refp->varScopep(vscp); + UINFO(4, "Fixed VarRef in SenTree: " << refp->varp()->name() << " -> " + << vscp->name() << endl); + } else { + refp->v3fatalSrc("Could not find VarScope for clock signal '" + << refp->varp()->name() << "' in scope " << m_scopep->name() + << " when creating covergroup sampling active"); + } + } + }); + + // Get or create the AstActive node for this sensitivity + // senTreep is a template used by getActive() which clones it into the AstActive; + // delete it afterwards as it is not added to the AST directly. + AstActive* const activep = m_namer.getActive(fl, senTreep); + VL_DO_DANGLING(senTreep->deleteTree(), senTreep); + + // Add the CMethodCall statement to the active domain + activep->addStmtsp(cmethodCallp->makeStmt()); + + UINFO(4, " Added automatic sample() call for covergroup " << varp->name() << endl); + } + + void visit(AstActive*) override {} // Don't iterate into actives + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit CovergroupSamplingVisitor(AstNetlist* nodep) { + // NOTE: Automatic sampling now works with --timing + // Previously disabled due to compatibility issues with V3Timing transformations + // The current implementation injects sampling before V3Active, allowing both modes to work + + UINFO(4, "CovergroupSamplingVisitor: Starting" << endl); + + // First pass: collect sample CFuncs from covergroup class scopes + m_inFirstPass = true; + iterate(nodep); + + // Second pass: add automatic sampling to covergroup instances + m_inFirstPass = false; + iterate(nodep); + + UINFO(4, "CovergroupSamplingVisitor: Complete" << endl); + } + ~CovergroupSamplingVisitor() override = default; +}; + //###################################################################### // Active class functions void V3Active::activeAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { ActiveVisitor{nodep}; } // Destruct before checking + { CovergroupSamplingVisitor{nodep}; } // Add automatic covergroup sampling + // Delete AstCovergroup nodes (event holders) left in covergroup classes by + // V3CoverageFunctional. They were kept in the AST to avoid orphaned SenTree nodes; + // now that V3Active has consumed them we can delete them. + for (AstNode* modp = nodep->modulesp(); modp; modp = modp->nextp()) { + if (AstClass* const classp = VN_CAST(modp, Class)) { + if (!classp->isCovergroup()) continue; + for (AstNode* memberp = classp->membersp(); memberp;) { + AstNode* const nextp = memberp->nextp(); + if (AstCovergroup* const cgp = VN_CAST(memberp, Covergroup)) { + cgp->unlinkFrBack(); + VL_DO_DANGLING(cgp->deleteTree(), cgp); + } + memberp = nextp; + } + } + } V3Global::dumpCheckGlobalTree("active", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index bf5ba0ae6..f9ae88a0d 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1052,6 +1052,113 @@ inline std::ostream& operator<<(std::ostream& os, const VCastable& rhs) { //###################################################################### +class VCoverBinsType final { +public: + enum en : uint8_t { + USER, + ARRAY, + AUTO, + BINS_IGNORE, // Renamed to avoid Windows macro conflict + BINS_ILLEGAL, // Renamed to avoid Windows macro conflict + DEFAULT, + BINS_WILDCARD, // Renamed to avoid Windows macro conflict + TRANSITION + }; + enum en m_e; + VCoverBinsType() + : m_e{USER} {} + // cppcheck-suppress noExplicitConstructor + constexpr VCoverBinsType(en _e) + : m_e{_e} {} + explicit VCoverBinsType(int _e) + : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning + constexpr operator en() const { return m_e; } + const char* ascii() const { + static const char* const names[] + = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; + return names[m_e]; + } +}; +constexpr bool operator==(const VCoverBinsType& lhs, const VCoverBinsType& rhs) { + return lhs.m_e == rhs.m_e; +} +constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { + return lhs.m_e == rhs; +} +constexpr bool operator==(VCoverBinsType::en lhs, const VCoverBinsType& rhs) { + return lhs == rhs.m_e; +} + +//###################################################################### + +class VCoverOptionType final { +public: + enum en : uint8_t { WEIGHT, GOAL, AT_LEAST, AUTO_BIN_MAX, PER_INSTANCE, COMMENT }; + enum en m_e; + VCoverOptionType() + : m_e{WEIGHT} {} + // cppcheck-suppress noExplicitConstructor + constexpr VCoverOptionType(en _e) + : m_e{_e} {} + explicit VCoverOptionType(int _e) + : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning + constexpr operator en() const { return m_e; } + const char* ascii() const { + static const char* const names[] + = {"weight", "goal", "at_least", "auto_bin_max", "per_instance", "comment"}; + return names[m_e]; + } +}; +constexpr bool operator==(const VCoverOptionType& lhs, const VCoverOptionType& rhs) { + return lhs.m_e == rhs.m_e; +} +constexpr bool operator==(const VCoverOptionType& lhs, VCoverOptionType::en rhs) { + return lhs.m_e == rhs; +} +constexpr bool operator==(VCoverOptionType::en lhs, const VCoverOptionType& rhs) { + return lhs == rhs.m_e; +} + +//###################################################################### + +class VTransRepType final { +public: + enum en : uint8_t { + NONE, // No repetition + CONSEC, // Consecutive repetition [*] + GOTO, // Goto repetition [->] + NONCONS // Nonconsecutive repetition [=] + }; + enum en m_e; + VTransRepType() + : m_e{NONE} {} + // cppcheck-suppress noExplicitConstructor + constexpr VTransRepType(en _e) + : m_e{_e} {} + explicit VTransRepType(int _e) + : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning + constexpr operator en() const { return m_e; } + const char* ascii() const { + static const char* const names[] = {"", "[*]", "[->]", "[=]"}; + return names[m_e]; + } + const char* asciiJson() const { + static const char* const names[] = {"", "\"consec\"", "\"goto\"", "\"noncons\""}; + return names[m_e]; + } +}; +constexpr bool operator==(const VTransRepType& lhs, const VTransRepType& rhs) { + return lhs.m_e == rhs.m_e; +} +constexpr bool operator==(const VTransRepType& lhs, VTransRepType::en rhs) { + return lhs.m_e == rhs; +} +constexpr bool operator==(VTransRepType::en lhs, const VTransRepType& rhs) { + return lhs == rhs.m_e; +} + +//###################################################################### + class VDirection final { public: enum en : uint8_t { NONE, INPUT, OUTPUT, INOUT, REF, CONSTREF }; diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 664d7946b..6da7f265c 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -719,6 +719,7 @@ public: string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } + bool isExprCoverageEligible() const override { return false; } const char* broken() const override { BROKEN_RTN(!VN_IS(backp(), NodeAssign)); // V3Emit* assumption return nullptr; diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 4a2e03f65..e6f75bb1d 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -250,6 +250,20 @@ public: string name() const override VL_MT_STABLE { return m_name; } bool sameNode(const AstNode* /*samep*/) const override { return true; } }; +class AstNodeFuncCovItem VL_NOT_FINAL : public AstNode { + // Base class for functional coverage items (coverpoints, crosses) +protected: + string m_name; + +public: + AstNodeFuncCovItem(VNType t, FileLine* fl, const string& name) + : AstNode{t, fl} + , m_name{name} {} + ASTGEN_MEMBERS_AstNodeFuncCovItem; + string name() const override VL_MT_STABLE { return m_name; } + void name(const string& flag) override { m_name = flag; } + bool maybePointedTo() const override { return true; } +}; class AstNodeGen VL_NOT_FINAL : public AstNode { // Generate construct public: @@ -506,6 +520,7 @@ class AstCFunc final : public AstNode { bool m_recursive : 1; // Recursive or part of recursion bool m_noLife : 1; // Disable V3Life on this function - has multiple calls, and reads Syms // state + bool m_isCovergroupSample : 1; // Automatic covergroup sample() function int m_cost; // Function call cost public: AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType = "") @@ -536,6 +551,7 @@ public: m_dpiImportWrapper = false; m_recursive = false; m_noLife = false; + m_isCovergroupSample = false; m_cost = v3Global.opt.instrCountDpi(); // As proxy for unknown general DPI cost } ASTGEN_MEMBERS_AstCFunc; @@ -611,6 +627,8 @@ public: bool recursive() const { return m_recursive; } void noLife(bool flag) { m_noLife = flag; } bool noLife() const { return m_noLife; } + bool isCovergroupSample() const { return m_isCovergroupSample; } + void isCovergroupSample(bool flag) { m_isCovergroupSample = flag; } void cost(int cost) { m_cost = cost; } // Special methods bool emptyBody() const { @@ -1013,6 +1031,175 @@ public: bool isPredictOptimizable() const override { return false; } bool sameNode(const AstNode* /*samep*/) const override { return true; } }; + +// Forward declarations for types used in constructors below +class AstCoverTransSet; +class AstCoverSelectExpr; + +class AstCoverBin final : public AstNode { + // @astgen op1 := rangesp : List[AstNode] + // @astgen op2 := iffp : Optional[AstNodeExpr] + // @astgen op3 := arraySizep : Optional[AstNodeExpr] + // @astgen op4 := transp : List[AstCoverTransSet] + string m_name; + VCoverBinsType m_type; + bool m_isArray = false; + +public: + AstCoverBin(FileLine* fl, const string& name, AstNode* rangesp, bool isIgnore, bool isIllegal, + bool isWildcard = false) + : ASTGEN_SUPER_CoverBin(fl) + , m_name{name} + , m_type{isWildcard ? VCoverBinsType::BINS_WILDCARD + : (isIllegal ? VCoverBinsType::BINS_ILLEGAL + : (isIgnore ? VCoverBinsType::BINS_IGNORE + : VCoverBinsType::USER))} { + if (rangesp) addRangesp(rangesp); + } + // Constructor for automatic bins + AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep) + : ASTGEN_SUPER_CoverBin(fl) + , m_name{name} + , m_type{VCoverBinsType::AUTO} + , m_isArray{true} { + this->arraySizep(arraySizep); + } + // Constructor for default bins (catch-all) + AstCoverBin(FileLine* fl, const string& name, VCoverBinsType type) + : ASTGEN_SUPER_CoverBin(fl) + , m_name{name} + , m_type{type} {} + // Constructor for transition bins + AstCoverBin(FileLine* fl, const string& name, AstCoverTransSet* transp, bool isIgnore, + bool isIllegal, bool isArrayBin = false) + : ASTGEN_SUPER_CoverBin(fl) + , m_name{name} + , m_type{isIllegal ? VCoverBinsType::BINS_ILLEGAL + : (isIgnore ? VCoverBinsType::BINS_IGNORE : VCoverBinsType::TRANSITION)} + , m_isArray{isArrayBin} { + if (transp) addTransp(transp); + } + ASTGEN_MEMBERS_AstCoverBin; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + string name() const override VL_MT_STABLE { return m_name; } + VCoverBinsType binsType() const { return m_type; } + bool isArray() const { return m_isArray; } + void isArray(bool flag) { m_isArray = flag; } +}; +class AstCoverCrossBins final : public AstNode { + // @astgen op1 := selectp : Optional[AstCoverSelectExpr] + string m_name; + +public: + AstCoverCrossBins(FileLine* fl, const string& name, AstCoverSelectExpr* selectp) + : ASTGEN_SUPER_CoverCrossBins(fl) + , m_name{name} { + this->selectp(selectp); + } + ASTGEN_MEMBERS_AstCoverCrossBins; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + string name() const override VL_MT_STABLE { return m_name; } +}; +class AstCoverOption final : public AstNode { + // @astgen op1 := valuep : AstNodeExpr + VCoverOptionType m_type; + +public: + AstCoverOption(FileLine* fl, VCoverOptionType type, AstNodeExpr* valuep) + : ASTGEN_SUPER_CoverOption(fl) + , m_type{type} { + this->valuep(valuep); + } + ASTGEN_MEMBERS_AstCoverOption; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + VCoverOptionType optionType() const { return m_type; } +}; +class AstCoverSelectExpr final : public AstNode { + // @astgen op1 := exprp : AstNodeExpr +public: + AstCoverSelectExpr(FileLine* fl, AstNodeExpr* exprp) + : ASTGEN_SUPER_CoverSelectExpr(fl) { + this->exprp(exprp); + } + ASTGEN_MEMBERS_AstCoverSelectExpr; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; +}; +class AstCoverTransItem final : public AstNode { + // Represents a single transition item: value or value[*N] or value[->N] or value[=N] + // @astgen op1 := valuesp : List[AstNode] + // @astgen op2 := repMinp : Optional[AstNodeExpr] + // @astgen op3 := repMaxp : Optional[AstNodeExpr] + VTransRepType m_repType; + +public: + AstCoverTransItem(FileLine* fl, AstNode* valuesp, VTransRepType repType = VTransRepType::NONE) + : ASTGEN_SUPER_CoverTransItem(fl) + , m_repType{repType} { + if (valuesp) addValuesp(valuesp); + } + ASTGEN_MEMBERS_AstCoverTransItem; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + VTransRepType repType() const { return m_repType; } +}; +class AstCoverTransSet final : public AstNode { + // Represents a transition set: value1 => value2 => value3 + // @astgen op1 := itemsp : List[AstCoverTransItem] +public: + AstCoverTransSet(FileLine* fl, AstCoverTransItem* itemsp) + : ASTGEN_SUPER_CoverTransSet(fl) { + if (itemsp) addItemsp(itemsp); + } + ASTGEN_MEMBERS_AstCoverTransSet; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; +}; +class AstCovergroup final : public AstNode { + // @astgen op1 := argsp : List[AstVar] + // @astgen op2 := membersp : List[AstNode] + // @astgen op3 := eventp : Optional[AstSenTree] + // @astgen op4 := sampleArgsp : List[AstVar] + string m_name; + bool m_isClass = false; + +public: + AstCovergroup(FileLine* fl, const string& name, AstVar* argsp, AstVar* sampleArgsp, + AstNode* membersp, AstSenTree* eventp) + : ASTGEN_SUPER_Covergroup(fl) + , m_name{name} { + if (argsp) addArgsp(argsp); + if (sampleArgsp) addSampleArgsp(sampleArgsp); + if (membersp) addMembersp(membersp); + this->eventp(eventp); + } + ASTGEN_MEMBERS_AstCovergroup; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + string name() const override VL_MT_STABLE { return m_name; } + void name(const string& name) override { m_name = name; } + bool isClass() const { return m_isClass; } + void isClass(bool flag) { m_isClass = flag; } + bool maybePointedTo() const override { return true; } +}; +class AstCoverpointRef final : public AstNode { + // @astgen ptr := m_coverpointp : Optional[AstCoverpoint] + string m_name; + +public: + AstCoverpointRef(FileLine* fl, const string& name) + : ASTGEN_SUPER_CoverpointRef(fl) + , m_name{name} {} + ASTGEN_MEMBERS_AstCoverpointRef; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + string name() const override VL_MT_STABLE { return m_name; } + AstCoverpoint* coverpointp() const { return m_coverpointp; } + void coverpointp(AstCoverpoint* nodep) { m_coverpointp = nodep; } +}; class AstDefParam final : public AstNode { // A defparam assignment // Parents: MODULE @@ -2495,6 +2682,33 @@ public: void dump(std::ostream& str = std::cout) const override; void dumpJson(std::ostream& str = std::cout) const override; }; +class AstCoverCross final : public AstNodeFuncCovItem { + // @astgen op1 := itemsp : List[AstCoverpointRef] + // @astgen op2 := binsp : List[AstCoverCrossBins] + // @astgen op3 := optionsp : List[AstCoverOption] +public: + AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp) + : ASTGEN_SUPER_CoverCross(fl, name) { + if (itemsp) addItemsp(itemsp); + } + ASTGEN_MEMBERS_AstCoverCross; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; +}; +class AstCoverpoint final : public AstNodeFuncCovItem { + // @astgen op1 := exprp : AstNodeExpr + // @astgen op2 := binsp : List[AstCoverBin] + // @astgen op3 := iffp : Optional[AstNodeExpr] + // @astgen op4 := optionsp : List[AstCoverOption] +public: + AstCoverpoint(FileLine* fl, const string& name, AstNodeExpr* exprp) + : ASTGEN_SUPER_Coverpoint(fl, name) { + this->exprp(exprp); + } + ASTGEN_MEMBERS_AstCoverpoint; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; +}; // === AstNodeGen === class AstGenBlock final : public AstNodeGen { @@ -2577,6 +2791,8 @@ class AstClass final : public AstNodeModule { bool m_needRNG = false; // Need RNG, uses srandom/randomize bool m_useVirtualPublic = false; // Subclasses need virtual public as uses interface class bool m_virtual = false; // Virtual class + // Covergroup options (when m_covergroup is true) + int m_cgAutoBinMax = -1; // option.auto_bin_max value (-1 = not set, use default 64) public: AstClass(FileLine* fl, const string& name, const string& libname) @@ -2604,6 +2820,9 @@ public: void needRNG(bool flag) { m_needRNG = flag; } bool useVirtualPublic() const { return m_useVirtualPublic; } void useVirtualPublic(bool flag) { m_useVirtualPublic = flag; } + // Covergroup options accessors + int cgAutoBinMax() const { return m_cgAutoBinMax; } + void cgAutoBinMax(int value) { m_cgAutoBinMax = value; } // Return true if this class is an extension of base class (SLOW) // Accepts nullptrs static bool isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index de8e06d64..76e52e235 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3443,3 +3443,89 @@ const char* AstNot::widthMismatch() const VL_MT_STABLE { BROKEN_RTN(lhsp()->widthMin() != widthMin()); return nullptr; } + +//###################################################################### +// Functional coverage dump methods + +void AstCovergroup::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " " << m_name; + if (m_isClass) str << " [class]"; +} + +void AstCovergroup::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isClass); + dumpJsonGen(str); +} + +void AstCoverpoint::dump(std::ostream& str) const { this->AstNodeFuncCovItem::dump(str); } + +void AstCoverpoint::dumpJson(std::ostream& str) const { this->AstNodeFuncCovItem::dumpJson(str); } + +void AstCoverBin::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " " << m_name << " " << m_type.ascii(); + if (m_isArray) str << "[]"; +} + +void AstCoverBin::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + str << ", \"name\": " << VString::quotePercent(m_name); + str << ", \"binsType\": \"" << m_type.ascii() << "\""; + if (m_isArray) str << ", \"isArray\": true"; +} + +void AstCoverTransItem::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (m_repType != VTransRepType::NONE) str << " " << m_repType.ascii(); +} + +void AstCoverTransItem::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + if (m_repType != VTransRepType::NONE) { str << ", \"repType\": " << m_repType.asciiJson(); } +} + +void AstCoverTransSet::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " trans_set"; +} + +void AstCoverTransSet::dumpJson(std::ostream& str) const { this->AstNode::dumpJson(str); } + +void AstCoverCross::dump(std::ostream& str) const { this->AstNodeFuncCovItem::dump(str); } + +void AstCoverCross::dumpJson(std::ostream& str) const { this->AstNodeFuncCovItem::dumpJson(str); } + +void AstCoverCrossBins::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " " << m_name; +} + +void AstCoverCrossBins::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + str << ", \"name\": " << VString::quotePercent(m_name); +} + +void AstCoverOption::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " " << m_type.ascii(); +} + +void AstCoverOption::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + str << ", \"optionType\": \"" << m_type.ascii() << "\""; +} + +void AstCoverpointRef::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " " << m_name; +} + +void AstCoverpointRef::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + str << ", \"name\": " << VString::quotePercent(m_name); +} + +void AstCoverSelectExpr::dump(std::ostream& str) const { this->AstNode::dump(str); } + +void AstCoverSelectExpr::dumpJson(std::ostream& str) const { this->AstNode::dumpJson(str); } diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index ccb3c9326..d97697174 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -797,7 +797,6 @@ class CoverageVisitor final : public VNVisitor { pair.first->second = varp; if (m_ftaskp) { varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); m_ftaskp->stmtsp()->addHereThisAsNext(varp); } else { m_modp->stmtsp()->addHereThisAsNext(varp); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp new file mode 100644 index 000000000..de6a68a52 --- /dev/null +++ b/src/V3Covergroup.cpp @@ -0,0 +1,1896 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Functional coverage implementation +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// FUNCTIONAL COVERAGE TRANSFORMATIONS: +// For each covergroup (AstClass with isCovergroup()): +// For each coverpoint (AstCoverpoint): +// Generate member variable for VerilatedCoverpoint +// Generate initialization in constructor +// Generate sample code in sample() method +// +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Covergroup.h" + +#include "V3MemberMap.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// Functional coverage visitor + +class FunctionalCoverageVisitor final : public VNVisitor { + // STATE + AstClass* m_covergroupp = nullptr; // Current covergroup being processed + AstFunc* m_sampleFuncp = nullptr; // Current sample() function + AstFunc* m_constructorp = nullptr; // Current constructor + std::vector m_coverpoints; // Coverpoints in current covergroup + std::vector m_coverCrosses; // Cross coverage items in current covergroup + + // Structure to track bins with their variables and options + struct BinInfo final { + AstCoverBin* binp; + AstVar* varp; + int atLeast; // Minimum hits required for coverage (from option.at_least) + AstCoverpoint* coverpointp; // Associated coverpoint (or nullptr for cross bins) + AstCoverCross* crossp; // Associated cross (or nullptr for coverpoint bins) + BinInfo(AstCoverBin* b, AstVar* v, int al = 1, AstCoverpoint* cp = nullptr, + AstCoverCross* cr = nullptr) + : binp{b} + , varp{v} + , atLeast{al} + , coverpointp{cp} + , crossp{cr} {} + }; + std::vector m_binInfos; // All bins in current covergroup + + // Track coverpoints that need previous value tracking (for transition bins) + std::map m_prevValueVars; // coverpoint -> prev_value variable + + // Track sequence state variables for multi-value transition bins + // Key is bin pointer, value is state position variable + std::map m_seqStateVars; // transition bin -> sequence state variable + + VMemberMap m_memberMap; // Member names cached for fast lookup + + // METHODS + void clearBinInfos() { + // Delete pseudo-bins created for cross coverage (they're never inserted into the AST) + for (const BinInfo& bi : m_binInfos) { + if (!bi.coverpointp && bi.crossp && bi.binp) { + VL_DO_DANGLING(bi.binp->deleteTree(), bi.binp); + } + } + m_binInfos.clear(); + } + + void processCovergroup() { + if (!m_covergroupp) return; + + UINFO(4, "Processing covergroup: " << m_covergroupp->name() << " with " + << m_coverpoints.size() << " coverpoints and " + << m_coverCrosses.size() << " crosses" << endl); + + // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) + clearBinInfos(); + + // For each coverpoint, generate sampling code + for (AstCoverpoint* cpp : m_coverpoints) { generateCoverpointCode(cpp); } + + // For each cross, generate sampling code + for (AstCoverCross* crossp : m_coverCrosses) { generateCrossCode(crossp); } + + // Generate coverage computation code (even for empty covergroups) + generateCoverageComputationCode(); + + // TODO: Generate instance registry infrastructure for static get_coverage() + // This requires: + // - Static registry members (t_instances, s_mutex) + // - registerInstance() / unregisterInstance() methods + // - Proper C++ emission in EmitC backend + // For now, get_coverage() returns 0.0 (placeholder) + + // Generate coverage database registration if coverage is enabled + if (v3Global.opt.coverage()) { generateCoverageRegistration(); } + + // Clean up orphaned cross pseudo-bins now that we're done with them + clearBinInfos(); + } + + void expandAutomaticBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { + // Find and expand any automatic bins + AstNode* prevBinp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp;) { + AstCoverBin* const cbinp = VN_CAST(binp, CoverBin); + AstNode* nextBinp = binp->nextp(); + + if (cbinp && cbinp->binsType() == VCoverBinsType::AUTO) { + UINFO(4, " Expanding automatic bin: " << cbinp->name() << endl); + + // Get array size - must be a constant + AstNodeExpr* sizep = cbinp->arraySizep(); + if (!sizep) { + cbinp->v3error("Automatic bins requires array size [N]"); // LCOV_EXCL_LINE + binp = nextBinp; + continue; + } + + // Evaluate as constant + const AstConst* constp = VN_CAST(sizep, Const); + if (!constp) { + cbinp->v3error("Automatic bins array size must be a constant"); + binp = nextBinp; + continue; + } + + const int numBins = constp->toSInt(); + if (numBins <= 0 || numBins > 10000) { + cbinp->v3error("Automatic bins array size must be 1-10000, got " + + std::to_string(numBins)); + binp = nextBinp; + continue; + } + + // Calculate range division + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); + const uint64_t binSize = (maxVal + 1) / numBins; + + UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins + << " binSize=" << binSize << endl); + + // Create expanded bins + for (int i = 0; i < numBins; i++) { + const uint64_t lo = i * binSize; + const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1); + + // Create constants for range + AstConst* loConstp + = new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, lo)}; + AstConst* hiConstp + = new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, hi)}; + + // Create InsideRange [lo:hi] + AstInsideRange* rangep + = new AstInsideRange{cbinp->fileline(), loConstp, hiConstp}; + rangep->dtypeFrom(exprp); // Set dtype from coverpoint expression + + // Create new bin + const string binName = cbinp->name() + "[" + std::to_string(i) + "]"; + AstCoverBin* newBinp + = new AstCoverBin{cbinp->fileline(), binName, rangep, false, false}; + + // Insert after previous bin + if (prevBinp) { + prevBinp->addNext(newBinp); + } else { + coverpointp->addBinsp(newBinp); + } + prevBinp = newBinp; + } + + // Remove the AUTO bin from the list + binp->unlinkFrBack(); + VL_DO_DANGLING(binp->deleteTree(), binp); + } else { + prevBinp = binp; + } + + binp = nextBinp; + } + } + + // Extract option values from a coverpoint + int getCoverpointAtLeast(AstCoverpoint* coverpointp) { + // Look for option.at_least in coverpoint options + for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) { + if (AstCoverOption* optp = VN_CAST(optionp, CoverOption)) { + if (optp->optionType() == VCoverOptionType::AT_LEAST) { + // Extract the value from the option expression + if (AstConst* constp = VN_CAST(optp->valuep(), Const)) { + return constp->toSInt(); + } + } + } + } + return 1; // Default: at least 1 hit required + } + + // Get auto_bin_max option value (check coverpoint options, then covergroup) + int getAutoBinMax(AstCoverpoint* coverpointp) { + // Check coverpoint options first + for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) { + if (AstCoverOption* optp = VN_CAST(optionp, CoverOption)) { + if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) { + if (AstConst* constp = VN_CAST(optp->valuep(), Const)) { + return constp->toSInt(); + } + } + } + } + // Check covergroup-level option stored in AstClass + if (m_covergroupp && m_covergroupp->cgAutoBinMax() >= 0) { + // Value was explicitly set (>= 0) + return m_covergroupp->cgAutoBinMax(); + } + return 64; // Default per IEEE 1800-2017 + } + + // Extract values to exclude from automatic bins (from ignore_bins and illegal_bins) + std::set getExcludedValues(AstCoverpoint* coverpointp) { + std::set excluded; + + // Scan existing bins for ignore/illegal types + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* cbinp = VN_CAST(binp, CoverBin); + if (!cbinp) continue; + + VCoverBinsType btype = cbinp->binsType(); + if (btype != VCoverBinsType::BINS_IGNORE && btype != VCoverBinsType::BINS_ILLEGAL) { + continue; + } + + // Extract values from the bin's range expression + if (AstNode* rangep = cbinp->rangesp()) { extractValuesFromRange(rangep, excluded); } + } + + return excluded; + } + + // Extract individual values from a range expression + void extractValuesFromRange(AstNode* nodep, std::set& values) { + if (!nodep) return; + + if (AstConst* constp = VN_CAST(nodep, Const)) { + // Single constant value + values.insert(constp->toUQuad()); + } else if (AstInsideRange* rangep = VN_CAST(nodep, InsideRange)) { + // Range [lo:hi] + AstConst* loConstp = VN_CAST(rangep->lhsp(), Const); + AstConst* hiConstp = VN_CAST(rangep->rhsp(), Const); + if (loConstp && hiConstp) { + uint64_t lo = loConstp->toUQuad(); + uint64_t hi = hiConstp->toUQuad(); + // Add all values in range (but limit to reasonable size) + if (hi - lo < 1000) { // Sanity check + for (uint64_t v = lo; v <= hi && v <= lo + 1000; v++) { values.insert(v); } + } + } + } + + // Recurse into list of nodes + extractValuesFromRange(nodep->op1p(), values); + extractValuesFromRange(nodep->op2p(), values); + extractValuesFromRange(nodep->op3p(), values); + extractValuesFromRange(nodep->op4p(), values); + } + + // Check if coverpoint has any regular bins (not just ignore/illegal) + bool hasRegularBins(AstCoverpoint* coverpointp) { + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (AstCoverBin* cbinp = VN_CAST(binp, CoverBin)) { + VCoverBinsType btype = cbinp->binsType(); + if (btype != VCoverBinsType::BINS_IGNORE + && btype != VCoverBinsType::BINS_ILLEGAL) { + return true; + } + } + } + return false; + } + + // Create implicit automatic bins when coverpoint has no explicit regular bins + void createImplicitAutoBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { + // If already has regular bins, nothing to do + if (hasRegularBins(coverpointp)) return; + + UINFO(4, " Creating implicit automatic bins for coverpoint: " << coverpointp->name() + << endl); + + // Get excluded values from ignore_bins and illegal_bins + std::set excluded = getExcludedValues(coverpointp); + + if (!excluded.empty()) { + UINFO(4, " Found " << excluded.size() << " excluded values" << endl); + } + + // Get auto_bin_max option + const int autoBinMax = getAutoBinMax(coverpointp); + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); + const uint64_t numTotalValues = (width >= 64) ? UINT64_MAX : (1ULL << width); + const uint64_t numValidValues = numTotalValues - excluded.size(); + + // Determine number of bins to create (based on non-excluded values) + int numBins; + if (numValidValues <= static_cast(autoBinMax)) { + // Create one bin per valid value + numBins = numValidValues; + } else { + // Create autoBinMax bins, dividing range + numBins = autoBinMax; + } + + UINFO(4, " Width=" << width << " numTotalValues=" << numTotalValues + << " numValidValues=" << numValidValues << " autoBinMax=" + << autoBinMax << " creating " << numBins << " bins" << endl); + + // Strategy: Create bins for each value (if numValidValues <= autoBinMax) + // or create range bins that avoid excluded values + if (numValidValues <= static_cast(autoBinMax)) { + // Create one bin per valid value + int binCount = 0; + for (uint64_t v = 0; v <= maxVal && binCount < numBins; v++) { + // Skip excluded values + if (excluded.find(v) != excluded.end()) continue; + + // Create single-value bin + AstConst* valConstp = new AstConst{coverpointp->fileline(), + V3Number(coverpointp->fileline(), width, v)}; + AstConst* valConstp2 = new AstConst{coverpointp->fileline(), + V3Number(coverpointp->fileline(), width, v)}; + + AstInsideRange* rangep + = new AstInsideRange{coverpointp->fileline(), valConstp, valConstp2}; + rangep->dtypeFrom(exprp); + + const string binName = "auto_" + std::to_string(binCount); + AstCoverBin* newBinp + = new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false}; + + coverpointp->addBinsp(newBinp); + binCount++; + } + UINFO(4, " Created " << binCount << " single-value automatic bins" << endl); + } else { + // Create range bins (more complex - need to handle excluded values in ranges) + // For simplicity, create bins and let excluded values not match any bin + const uint64_t binSize = (maxVal + 1) / numBins; + + for (int i = 0; i < numBins; i++) { + const uint64_t lo = i * binSize; + const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1); + + // Check if entire range is excluded + bool anyValid = false; + for (uint64_t v = lo; v <= hi && v <= lo + 1000; v++) { + if (excluded.find(v) == excluded.end()) { + anyValid = true; + break; + } + } + + if (!anyValid && (hi - lo < 1000)) { + // Skip this bin entirely if all values are excluded + UINFO(4, " Skipping bin [" << lo << ":" << hi << "] - all values excluded" + << endl); + continue; + } + + // Create constants for range + AstConst* loConstp = new AstConst{coverpointp->fileline(), + V3Number(coverpointp->fileline(), width, lo)}; + AstConst* hiConstp = new AstConst{coverpointp->fileline(), + V3Number(coverpointp->fileline(), width, hi)}; + + // Create InsideRange [lo:hi] + AstInsideRange* rangep + = new AstInsideRange{coverpointp->fileline(), loConstp, hiConstp}; + rangep->dtypeFrom(exprp); + + // Create bin name + const string binName = "auto_" + std::to_string(i); + AstCoverBin* newBinp + = new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false}; + + // Add to coverpoint + coverpointp->addBinsp(newBinp); + } + + UINFO(4, " Created range-based automatic bins" << endl); + } + } + + // Check if coverpoint has any transition bins and create previous value variable if needed + bool hasTransitionBins(AstCoverpoint* coverpointp) { + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* cbinp = VN_CAST(binp, CoverBin); + if (cbinp && cbinp->binsType() == VCoverBinsType::TRANSITION) { return true; } + } + return false; + } + + // Create previous value variable for transition tracking + AstVar* createPrevValueVar(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { + // Check if already created + auto it = m_prevValueVars.find(coverpointp); + if (it != m_prevValueVars.end()) { return it->second; } + + // Create variable to store previous sampled value + const string varName = "__Vprev_" + coverpointp->name(); + AstVar* prevVarp + = new AstVar{coverpointp->fileline(), VVarType::MEMBER, varName, exprp->dtypep()}; + prevVarp->isStatic(false); + m_covergroupp->addMembersp(prevVarp); + + UINFO(4, " Created previous value variable: " << varName << endl); + + // Initialize to zero in constructor + AstNodeExpr* initExprp + = new AstConst{prevVarp->fileline(), AstConst::WidthedValue{}, prevVarp->width(), 0}; + AstNodeStmt* initStmtp = new AstAssign{ + prevVarp->fileline(), new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE}, + initExprp}; + m_constructorp->addStmtsp(initStmtp); + + m_prevValueVars[coverpointp] = prevVarp; + return prevVarp; + } + + // Create state position variable for multi-value transition bins + // Tracks position in sequence: 0=not started, 1=seen first item, etc. + AstVar* createSequenceStateVar(AstCoverpoint* coverpointp, AstCoverBin* binp) { + // Check if already created + auto it = m_seqStateVars.find(binp); + if (it != m_seqStateVars.end()) { return it->second; } + + // Create variable to track sequence position + const string varName = "__Vseqpos_" + coverpointp->name() + "_" + binp->name(); + // Use 8-bit integer for state position (sequences rarely > 255 items) + AstVar* stateVarp + = new AstVar{binp->fileline(), VVarType::MEMBER, varName, VFlagLogicPacked{}, 8}; + stateVarp->isStatic(false); + m_covergroupp->addMembersp(stateVarp); + + UINFO(4, " Created sequence state variable: " << varName << endl); + + // Initialize to 0 (not started) in constructor + AstNodeStmt* initStmtp = new AstAssign{ + stateVarp->fileline(), new AstVarRef{stateVarp->fileline(), stateVarp, VAccess::WRITE}, + new AstConst{stateVarp->fileline(), AstConst::WidthedValue{}, 8, 0}}; + m_constructorp->addStmtsp(initStmtp); + + m_seqStateVars[binp] = stateVarp; + return stateVarp; + } + + void generateCoverpointCode(AstCoverpoint* coverpointp) { + if (!m_sampleFuncp || !m_constructorp) { + coverpointp->v3warn(E_UNSUPPORTED, + "Coverpoint without sample() or constructor"); // LCOV_EXCL_LINE + return; + } + + UINFO(4, " Generating code for coverpoint: " << coverpointp->name() << endl); + + // Get the coverpoint expression + AstNodeExpr* exprp = coverpointp->exprp(); + if (!exprp) { + coverpointp->v3warn(E_UNSUPPORTED, "Coverpoint without expression"); // LCOV_EXCL_LINE + return; + } + + // Expand automatic bins before processing + expandAutomaticBins(coverpointp, exprp); + + // Create implicit automatic bins if no regular bins exist + createImplicitAutoBins(coverpointp, exprp); + + // Extract option values for this coverpoint + int atLeastValue = getCoverpointAtLeast(coverpointp); + UINFO(6, " Coverpoint at_least = " << atLeastValue << endl); + + // Generate member variables and matching code for each bin + // Process in two passes: first non-default bins, then default bins + std::vector defaultBins; + int binCount = 0; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (++binCount > 1000) { + coverpointp->v3error("Too many bins or infinite loop detected in bin iteration"); + break; + } + AstCoverBin* const cbinp = VN_CAST(binp, CoverBin); + if (!cbinp) continue; + + // Defer default bins to second pass + if (cbinp->binsType() == VCoverBinsType::DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + + // Handle array bins: create separate bin for each value/transition + if (cbinp->isArray()) { + if (cbinp->binsType() == VCoverBinsType::TRANSITION) { + generateTransitionArrayBins(coverpointp, cbinp, exprp, atLeastValue); + } else { + generateArrayBins(coverpointp, cbinp, exprp, atLeastValue); + } + continue; + } + + // Create a member variable to track hits for this bin + // Sanitize bin name to make it a valid C++ identifier + string binName = cbinp->name(); + std::replace(binName.begin(), binName.end(), '[', '_'); + std::replace(binName.begin(), binName.end(), ']', '_'); + const string varName = "__Vcov_" + coverpointp->name() + "_" + binName; + AstVar* const varp = new AstVar{cbinp->fileline(), VVarType::MEMBER, varName, + cbinp->findUInt32DType()}; + varp->isStatic(false); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created member variable: " + << varName << " type=" << static_cast(cbinp->binsType()) + << (cbinp->binsType() == VCoverBinsType::BINS_IGNORE ? " (IGNORE)" + : cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL ? " (ILLEGAL)" + : " (USER)") + << endl); + + // Track this bin for coverage computation with at_least value + m_binInfos.push_back(BinInfo(cbinp, varp, atLeastValue, coverpointp)); + + // Note: Coverage database registration happens later via VL_COVER_INSERT + // (see generateCoverageDeclarations() method around line 1164) + // Classes use "v_covergroup/" hier prefix vs modules + + // Generate bin matching code in sample() + // Handle transition bins specially + if (cbinp->binsType() == VCoverBinsType::TRANSITION) { + generateTransitionBinMatchCode(coverpointp, cbinp, exprp, varp); + } else { + generateBinMatchCode(coverpointp, cbinp, exprp, varp); + } + } + + // Second pass: Handle default bins + // Default bin matches when value doesn't match any other explicit bin + for (AstCoverBin* defBinp : defaultBins) { + // Create member variable for default bin + string binName = defBinp->name(); + std::replace(binName.begin(), binName.end(), '[', '_'); + std::replace(binName.begin(), binName.end(), ']', '_'); + const string varName = "__Vcov_" + coverpointp->name() + "_" + binName; + AstVar* const varp = new AstVar{defBinp->fileline(), VVarType::MEMBER, varName, + defBinp->findUInt32DType()}; + varp->isStatic(false); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created default bin variable: " << varName << endl); + + // Track for coverage computation + m_binInfos.push_back(BinInfo(defBinp, varp, atLeastValue, coverpointp)); + + // Generate matching code: if (NOT (bin1 OR bin2 OR ... OR binN)) + generateDefaultBinMatchCode(coverpointp, defBinp, exprp, varp); + } + + // After all bins processed, if coverpoint has transition bins, update previous value + if (hasTransitionBins(coverpointp)) { + AstVar* prevVarp = m_prevValueVars[coverpointp]; + // Generate: __Vprev_cpname = current_value; + AstNodeStmt* updateStmtp + = new AstAssign{coverpointp->fileline(), + new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE}, + exprp->cloneTree(false)}; + m_sampleFuncp->addStmtsp(updateStmtp); + UINFO(4, " Added previous value update at end of sample()" << endl); + } + } + + void generateBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, AstNodeExpr* exprp, + AstVar* hitVarp) { + UINFO(4, " Generating bin match for: " << binp->name() << endl); + + // Build the bin matching condition using the shared function + AstNodeExpr* fullCondp = buildBinCondition(binp, exprp); + + if (!fullCondp) { + UINFO(4, " No valid conditions generated" << endl); + return; + } + + // Apply iff condition if present - wraps the bin match condition + if (AstNodeExpr* iffp = coverpointp->iffp()) { + UINFO(6, " Adding iff condition" << endl); + fullCondp = new AstAnd{binp->fileline(), iffp->cloneTree(false), fullCondp}; + } + + // Create the increment statement + AstNode* stmtp = new AstAssign{ + binp->fileline(), new AstVarRef{binp->fileline(), hitVarp, VAccess::WRITE}, + new AstAdd{binp->fileline(), new AstVarRef{binp->fileline(), hitVarp, VAccess::READ}, + new AstConst{binp->fileline(), AstConst::WidthedValue{}, 32, 1}}}; + + // For illegal_bins, add an error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal bin '" + binp->name() + "' hit in coverpoint '" + + coverpointp->name() + "'"; + AstDisplay* errorp = new AstDisplay{binp->fileline(), VDisplayType::DT_ERROR, errMsg, + nullptr, nullptr}; + errorp->fmtp()->timeunit(m_covergroupp->timeunit()); + stmtp = stmtp->addNext(errorp); + stmtp = stmtp->addNext(new AstStop{binp->fileline(), true}); + } + + // Create: if (condition) { hitVar++; [error if illegal] } + AstIf* const ifp = new AstIf{binp->fileline(), fullCondp, stmtp, nullptr}; + + UINFO(4, " Adding bin match if statement to sample function" << endl); + if (!m_sampleFuncp) + binp->v3fatalSrc("m_sampleFuncp is null when trying to add bin match code"); + m_sampleFuncp->addStmtsp(ifp); + UINFO(4, " Successfully added if statement for bin: " << binp->name() << endl); + } + + // Generate matching code for default bins + // Default bins match when value doesn't match any other explicit bin + void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, + AstNodeExpr* exprp, AstVar* hitVarp) { + UINFO(4, " Generating default bin match for: " << defBinp->name() << endl); + + // Build OR of all non-default, non-ignore bins + AstNodeExpr* anyBinMatchp = nullptr; + + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_CAST(binp, CoverBin); + if (!cbinp) continue; + + // Skip default, ignore, and illegal bins + if (cbinp->binsType() == VCoverBinsType::DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + continue; + } + + // Build condition for this bin + AstNodeExpr* binCondp = buildBinCondition(cbinp, exprp); + if (!binCondp) continue; + + // OR with previous conditions + if (anyBinMatchp) { + anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; + } else { + anyBinMatchp = binCondp; + } + } + + // Default matches when NO explicit bin matches + AstNodeExpr* defaultCondp = nullptr; + if (anyBinMatchp) { + // NOT (bin1 OR bin2 OR ... OR binN) + defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; + } else { + // No other bins - default always matches (shouldn't happen in practice) + defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; + } + + // Apply iff condition if present + if (AstNodeExpr* iffp = coverpointp->iffp()) { + defaultCondp = new AstAnd{defBinp->fileline(), iffp->cloneTree(false), defaultCondp}; + } + + // Create increment statement + AstNode* stmtp = new AstAssign{ + defBinp->fileline(), new AstVarRef{defBinp->fileline(), hitVarp, VAccess::WRITE}, + new AstAdd{defBinp->fileline(), + new AstVarRef{defBinp->fileline(), hitVarp, VAccess::READ}, + new AstConst{defBinp->fileline(), AstConst::WidthedValue{}, 32, 1}}}; + + // Create if statement + AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr}; + + if (!m_sampleFuncp) defBinp->v3fatalSrc("m_sampleFuncp is null for default bin"); + m_sampleFuncp->addStmtsp(ifp); + UINFO(4, " Successfully added default bin if statement" << endl); + } + + // Generate matching code for transition bins + // Transition bins match sequences like: (val1 => val2 => val3) + void generateTransitionBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, + AstNodeExpr* exprp, AstVar* hitVarp) { + UINFO(4, " Generating transition bin match for: " << binp->name() << endl); + + // Get the (single) transition set + AstCoverTransSet* transSetp = binp->transp(); + if (!transSetp) { + binp->v3error("Transition bin without transition set"); // LCOV_EXCL_LINE + return; + } + + // Use the helper function to generate code for this transition + generateSingleTransitionCode(coverpointp, binp, exprp, hitVarp, transSetp); + } + + // Generate state machine code for multi-value transition sequences + // Handles transitions like (1 => 2 => 3 => 4) + void generateMultiValueTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp, + AstNodeExpr* exprp, AstVar* hitVarp, + const std::vector& items) { + UINFO(4, + " Generating multi-value transition state machine for: " << binp->name() << endl); + UINFO(4, " Sequence length: " << items.size() << " items" << endl); + + // Create state position variable + AstVar* stateVarp = createSequenceStateVar(coverpointp, binp); + + // Build case statement with N cases (one for each state 0 to N-1) + // State 0: Not started, looking for first item + // State 1 to N-1: In progress, looking for next item + + AstCase* casep + = new AstCase{binp->fileline(), VCaseType::CT_CASE, + new AstVarRef{stateVarp->fileline(), stateVarp, VAccess::READ}, nullptr}; + + // Generate each case item in the switch statement + for (size_t state = 0; state < items.size(); ++state) { + AstCaseItem* caseItemp = generateTransitionStateCase(coverpointp, binp, exprp, hitVarp, + stateVarp, items, state); + + if (caseItemp) { casep->addItemsp(caseItemp); } + } + + m_sampleFuncp->addStmtsp(casep); + UINFO(4, " Successfully added multi-value transition state machine" << endl); + } + + // Generate code for a single state in the transition state machine + // Returns the case item for this state + AstCaseItem* generateTransitionStateCase(AstCoverpoint* coverpointp, AstCoverBin* binp, + AstNodeExpr* exprp, AstVar* hitVarp, + AstVar* stateVarp, + const std::vector& items, + size_t state) { + FileLine* const fl = binp->fileline(); + + // Build condition for current value matching expected item at this state + AstNodeExpr* matchCondp + = buildTransitionItemCondition(items[state], exprp->cloneTree(false)); + if (!matchCondp) { + binp->v3error("Could not build transition condition for state " // LCOV_EXCL_LINE + + std::to_string(state)); // LCOV_EXCL_LINE + return nullptr; + } + + // Apply iff condition if present + if (AstNodeExpr* iffp = coverpointp->iffp()) { + matchCondp = new AstAnd{fl, iffp->cloneTree(false), matchCondp}; + } + + AstNodeStmt* matchActionp = nullptr; + + if (state == items.size() - 1) { + // Last state: sequence complete! + // Increment bin counter + matchActionp + = new AstAssign{fl, new AstVarRef{fl, hitVarp, VAccess::WRITE}, + new AstAdd{fl, new AstVarRef{fl, hitVarp, VAccess::READ}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}; + + // For illegal_bins, add error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal transition bin '" + binp->name() + + "' hit in coverpoint '" + coverpointp->name() + "'"; + AstDisplay* errorp + = new AstDisplay{fl, VDisplayType::DT_ERROR, errMsg, nullptr, nullptr}; + errorp->fmtp()->timeunit(m_covergroupp->timeunit()); + matchActionp = matchActionp->addNext(errorp); + matchActionp = matchActionp->addNext(new AstStop{fl, true}); + } + + // Reset state to 0 + matchActionp = matchActionp->addNext( + new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}); + } else { + // Intermediate state: advance to next state + matchActionp = new AstAssign{ + fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, static_cast(state + 1)}}; + } + + // Build restart logic: check if current value matches first item + // If so, restart sequence from state 1 (even if we're in middle of sequence) + AstNodeStmt* noMatchActionp = nullptr; + if (state > 0) { + // Check if current value matches first item (restart condition) + AstNodeExpr* restartCondp + = buildTransitionItemCondition(items[0], exprp->cloneTree(false)); + + if (restartCondp) { + // Apply iff condition + if (AstNodeExpr* iffp = coverpointp->iffp()) { + restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp}; + } + + // Restart to state 1 + AstNodeStmt* restartActionp + = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 1}}; + + // Reset to state 0 (else branch) + AstNodeStmt* resetActionp + = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}; + + noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp}; + } else { + // Can't build restart condition, just reset + noMatchActionp = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}; + } + } + // For state 0, no action needed if no match (stay in state 0) + + // Combine into if-else + AstNodeStmt* stmtp = new AstIf{fl, matchCondp, matchActionp, noMatchActionp}; + + // Create case item for this state value + AstCaseItem* caseItemp = new AstCaseItem{ + fl, new AstConst{fl, AstConst::WidthedValue{}, 8, static_cast(state)}, + stmtp}; + + return caseItemp; + } + + // Build condition for a single transition item + // Returns expression that checks if value matches the item's value/range list + AstNodeExpr* buildTransitionItemCondition(AstCoverTransItem* itemp, AstNodeExpr* exprp) { + AstNodeExpr* condp = nullptr; + + // Get values from the transition item + for (AstNode* valp = itemp->valuesp(); valp; valp = valp->nextp()) { + AstNodeExpr* singleCondp = nullptr; + + if (AstConst* constp = VN_CAST(valp, Const)) { + // Simple value: check equality + singleCondp = new AstEq{constp->fileline(), exprp->cloneTree(false), + constp->cloneTree(false)}; + } else if (AstRange* rangep = VN_CAST(valp, Range)) { + // Range [min:max]: check if value is in range (use signed if expr is signed) + if (exprp->isSigned()) { + singleCondp + = new AstAnd{rangep->fileline(), + new AstGteS{rangep->fileline(), exprp->cloneTree(false), + rangep->leftp()->cloneTree(false)}, + new AstLteS{rangep->fileline(), exprp->cloneTree(false), + rangep->rightp()->cloneTree(false)}}; + } else { + // For unsigned, skip >= 0 check as it's always true + AstNodeExpr* minExprp = rangep->leftp(); + AstNodeExpr* maxExprp = rangep->rightp(); + AstConst* minConstp = VN_CAST(minExprp, Const); + AstConst* maxConstp = VN_CAST(maxExprp, Const); + const int exprWidth = exprp->widthMin(); + bool skipLowerCheck = (minConstp && minConstp->toUQuad() == 0); + bool skipUpperCheck = false; + if (maxConstp && exprWidth > 0 && exprWidth <= 64) { + const uint64_t maxVal = (exprWidth == 64) ? ~static_cast(0) + : ((1ULL << exprWidth) - 1ULL); + skipUpperCheck = (maxConstp->toUQuad() == maxVal); + } + + if (skipLowerCheck && skipUpperCheck) { + singleCondp = new AstConst{rangep->fileline(), AstConst::BitTrue{}}; + } else if (skipLowerCheck) { + // Only check upper bound for [0:max] + singleCondp = new AstLte{rangep->fileline(), exprp->cloneTree(false), + maxExprp->cloneTree(false)}; + } else if (skipUpperCheck) { + // Only check lower bound when upper is maximal for the expression width + singleCondp = new AstGte{rangep->fileline(), exprp->cloneTree(false), + minExprp->cloneTree(false)}; + } else { + singleCondp + = new AstAnd{rangep->fileline(), + new AstGte{rangep->fileline(), exprp->cloneTree(false), + rangep->leftp()->cloneTree(false)}, + new AstLte{rangep->fileline(), exprp->cloneTree(false), + rangep->rightp()->cloneTree(false)}}; + } + } + } else if (AstInsideRange* inrangep = VN_CAST(valp, InsideRange)) { + // InsideRange [min:max]: similar to Range (use signed if expr is signed) + if (exprp->isSigned()) { + singleCondp + = new AstAnd{inrangep->fileline(), + new AstGteS{inrangep->fileline(), exprp->cloneTree(false), + inrangep->lhsp()->cloneTree(false)}, + new AstLteS{inrangep->fileline(), exprp->cloneTree(false), + inrangep->rhsp()->cloneTree(false)}}; + } else { + // For unsigned, skip >= 0 check as it's always true + AstNodeExpr* minExprp = inrangep->lhsp(); + AstNodeExpr* maxExprp = inrangep->rhsp(); + AstConst* minConstp = VN_CAST(minExprp, Const); + AstConst* maxConstp = VN_CAST(maxExprp, Const); + const int exprWidth = exprp->widthMin(); + bool skipLowerCheck = (minConstp && minConstp->toUQuad() == 0); + bool skipUpperCheck = false; + if (maxConstp && exprWidth > 0 && exprWidth <= 64) { + const uint64_t maxVal = (exprWidth == 64) ? ~static_cast(0) + : ((1ULL << exprWidth) - 1ULL); + skipUpperCheck = (maxConstp->toUQuad() == maxVal); + } + + if (skipLowerCheck && skipUpperCheck) { + singleCondp = new AstConst{inrangep->fileline(), AstConst::BitTrue{}}; + } else if (skipLowerCheck) { + // Only check upper bound for [0:max] + singleCondp = new AstLte{inrangep->fileline(), exprp->cloneTree(false), + maxExprp->cloneTree(false)}; + } else if (skipUpperCheck) { + // Only check lower bound when upper is maximal for the expression width + singleCondp = new AstGte{inrangep->fileline(), exprp->cloneTree(false), + minExprp->cloneTree(false)}; + } else { + singleCondp + = new AstAnd{inrangep->fileline(), + new AstGte{inrangep->fileline(), exprp->cloneTree(false), + inrangep->lhsp()->cloneTree(false)}, + new AstLte{inrangep->fileline(), exprp->cloneTree(false), + inrangep->rhsp()->cloneTree(false)}}; + } + } + } else { + // Unknown node type - try to handle as expression + UINFO(4, " Transition item has unknown value node type: " << valp->typeName() + << endl); + // For now, just skip unknown types - this prevents crashes + continue; + } + + // OR together multiple values + if (singleCondp) { + if (condp) { + condp = new AstOr{itemp->fileline(), condp, singleCondp}; + } else { + condp = singleCondp; + } + } + } + + if (!condp) { + // If no values were successfully processed, return nullptr + // The caller will handle this error + UINFO(4, " No valid transition conditions could be built" << endl); + } + + // Take ownership of exprp (used only for cloning above) + VL_DO_DANGLING(exprp->deleteTree(), exprp); + return condp; + } + + // Generate multiple bins for array bins + // Array bins create one bin per value in the range list + void generateArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp, AstNodeExpr* exprp, + int atLeastValue) { + UINFO(4, " Generating array bins for: " << arrayBinp->name() << endl); + + // Extract all values from the range list + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstRange* const rangenodep = VN_CAST(rangep, Range)) { + // For ranges [min:max], create bins for each value + AstConst* const minConstp = VN_CAST(rangenodep->leftp(), Const); + AstConst* const maxConstp = VN_CAST(rangenodep->rightp(), Const); + if (minConstp && maxConstp) { + int minVal = minConstp->toSInt(); + int maxVal = maxConstp->toSInt(); + for (int val = minVal; val <= maxVal; ++val) { + values.push_back( + new AstConst{rangenodep->fileline(), AstConst::Signed32{}, val}); + } + } else { + arrayBinp->v3error("covergroup value range"); + return; + } + } else if (AstInsideRange* const insideRangep = VN_CAST(rangep, InsideRange)) { + // For InsideRange [min:max], create bins for each value + AstConst* const minConstp = VN_CAST(insideRangep->lhsp(), Const); + AstConst* const maxConstp = VN_CAST(insideRangep->rhsp(), Const); + if (minConstp && maxConstp) { + int minVal = minConstp->toSInt(); + int maxVal = maxConstp->toSInt(); + UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]" + << endl); + for (int val = minVal; val <= maxVal; ++val) { + values.push_back( + new AstConst{insideRangep->fileline(), AstConst::Signed32{}, val}); + } + } else { + arrayBinp->v3error("covergroup value range"); + return; + } + } else { + // Single value - should be an expression + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } + } + + // Create a separate bin for each value + int index = 0; + for (AstNodeExpr* valuep : values) { + // Create bin name: originalName[index] + const string binName = arrayBinp->name() + "[" + std::to_string(index) + "]"; + const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index); + const string varName = "__Vcov_" + coverpointp->name() + "_" + sanitizedName; + + // Create member variable for this bin + AstVar* const varp = new AstVar{arrayBinp->fileline(), VVarType::MEMBER, varName, + arrayBinp->findUInt32DType()}; + varp->isStatic(false); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created array bin [" << index << "]: " << varName << endl); + + // Track for coverage computation + m_binInfos.push_back(BinInfo(arrayBinp, varp, atLeastValue, coverpointp)); + + // Generate matching code for this specific value + generateArrayBinMatchCode(coverpointp, arrayBinp, exprp, varp, valuep); + + ++index; + } + + UINFO(4, " Generated " << index << " array bins" << endl); + } + + // Generate matching code for a single array bin element + void generateArrayBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, + AstNodeExpr* exprp, AstVar* hitVarp, AstNodeExpr* valuep) { + // Create condition: expr == value + AstNodeExpr* condp = new AstEq{binp->fileline(), exprp->cloneTree(false), valuep}; + + // Apply iff condition if present + if (AstNodeExpr* iffp = coverpointp->iffp()) { + condp = new AstAnd{binp->fileline(), iffp->cloneTree(false), condp}; + } + + // Create increment statement + AstNode* stmtp = new AstAssign{ + binp->fileline(), new AstVarRef{binp->fileline(), hitVarp, VAccess::WRITE}, + new AstAdd{binp->fileline(), new AstVarRef{binp->fileline(), hitVarp, VAccess::READ}, + new AstConst{binp->fileline(), AstConst::WidthedValue{}, 32, 1}}}; + + // For illegal_bins, add error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal bin hit in coverpoint '" + coverpointp->name() + "'"; + AstDisplay* errorp = new AstDisplay{binp->fileline(), VDisplayType::DT_ERROR, errMsg, + nullptr, nullptr}; + errorp->fmtp()->timeunit(m_covergroupp->timeunit()); + stmtp = stmtp->addNext(errorp); + stmtp = stmtp->addNext(new AstStop{binp->fileline(), true}); + } + + // Create if statement + AstIf* const ifp = new AstIf{binp->fileline(), condp, stmtp, nullptr}; + + if (!m_sampleFuncp) binp->v3fatalSrc("m_sampleFuncp is null for array bin"); + m_sampleFuncp->addStmtsp(ifp); + } + + // Generate multiple bins for transition array bins + // Array bins with transitions create one bin per transition sequence + void generateTransitionArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp, + AstNodeExpr* exprp, int atLeastValue) { + UINFO(4, " Generating transition array bins for: " << arrayBinp->name() << endl); + + // Extract all transition sets + std::vector transSets; + for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp()) { + if (AstCoverTransSet* ts = VN_CAST(transSetp, CoverTransSet)) { + transSets.push_back(ts); + } + } + + if (transSets.empty()) { + arrayBinp->v3error("Transition array bin without transition sets"); // LCOV_EXCL_LINE + return; + } + + UINFO(4, " Found " << transSets.size() << " transition sets" << endl); + + // Create a separate bin for each transition sequence + int index = 0; + for (AstCoverTransSet* transSetp : transSets) { + // Create bin name: originalName[index] + const string binName = arrayBinp->name() + "[" + std::to_string(index) + "]"; + const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index); + const string varName = "__Vcov_" + coverpointp->name() + "_" + sanitizedName; + + // Create member variable for this bin + AstVar* const varp = new AstVar{arrayBinp->fileline(), VVarType::MEMBER, varName, + arrayBinp->findUInt32DType()}; + varp->isStatic(false); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created transition array bin [" << index << "]: " << varName << endl); + + // Track for coverage computation + m_binInfos.push_back(BinInfo(arrayBinp, varp, atLeastValue, coverpointp)); + + // Generate matching code for this specific transition + generateSingleTransitionCode(coverpointp, arrayBinp, exprp, varp, transSetp); + + ++index; + } + + UINFO(4, " Generated " << index << " transition array bins" << endl); + } + + // Generate code for a single transition sequence (used by both regular and array bins) + void generateSingleTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp, + AstNodeExpr* exprp, AstVar* hitVarp, + AstCoverTransSet* transSetp) { + UINFO(4, " Generating code for transition sequence" << endl); + + // Get or create previous value variable + AstVar* prevVarp = createPrevValueVar(coverpointp, exprp); + + if (!transSetp) { + binp->v3error("Transition bin without transition set"); // LCOV_EXCL_LINE + return; + } + + // Get transition items (the sequence: item1 => item2 => item3) + std::vector items; + for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp()) { + if (AstCoverTransItem* transp = VN_CAST(itemp, CoverTransItem)) { + items.push_back(transp); + } + } + + if (items.empty()) { + binp->v3error("Transition set without items"); + return; + } + + // Check for unsupported repetition operators + // Note: the grammar handles [*], [->], [=] at parse time via COVERIGN warning, + // resulting in null AstCoverTransItem nodes which are filtered out above. + // This check is therefore unreachable from normal SV parsing. + for (AstCoverTransItem* item : items) { // LCOV_EXCL_START + if (item->repType() != VTransRepType::NONE) { + binp->v3warn(E_UNSUPPORTED, + "Transition repetition operators ([*], [->], [=]) not yet supported"); + return; + } + } // LCOV_EXCL_STOP + + if (items.size() == 1) { + // Single item transition not valid (need at least 2 values for =>) + binp->v3error("Transition requires at least two values"); + return; + } else if (items.size() == 2) { + // Simple two-value transition: (val1 => val2) + // Use optimized direct comparison (no state machine needed) + AstNodeExpr* cond1p = buildTransitionItemCondition( + items[0], new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::READ}); + AstNodeExpr* cond2p = buildTransitionItemCondition(items[1], exprp->cloneTree(false)); + + if (!cond1p || !cond2p) { + binp->v3error("Could not build transition conditions"); // LCOV_EXCL_LINE + return; + } + + // Combine: prev matches val1 AND current matches val2 + AstNodeExpr* fullCondp = new AstAnd{binp->fileline(), cond1p, cond2p}; + + // Apply iff condition if present + if (AstNodeExpr* iffp = coverpointp->iffp()) { + fullCondp = new AstAnd{binp->fileline(), iffp->cloneTree(false), fullCondp}; + } + + // Create increment statement + AstNode* stmtp = new AstAssign{ + binp->fileline(), new AstVarRef{binp->fileline(), hitVarp, VAccess::WRITE}, + new AstAdd{binp->fileline(), + new AstVarRef{binp->fileline(), hitVarp, VAccess::READ}, + new AstConst{binp->fileline(), AstConst::WidthedValue{}, 32, 1}}}; + + // For illegal_bins, add an error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal transition bin '" + binp->name() + + "' hit in coverpoint '" + coverpointp->name() + "'"; + AstDisplay* errorp = new AstDisplay{binp->fileline(), VDisplayType::DT_ERROR, + errMsg, nullptr, nullptr}; + errorp->fmtp()->timeunit(m_covergroupp->timeunit()); + stmtp = stmtp->addNext(errorp); + stmtp = stmtp->addNext(new AstStop{binp->fileline(), true}); + } + + // Create if statement + AstIf* const ifp = new AstIf{binp->fileline(), fullCondp, stmtp, nullptr}; + m_sampleFuncp->addStmtsp(ifp); + + UINFO(4, " Successfully added 2-value transition if statement" << endl); + } else { + // Multi-value sequence (a => b => c => ...) + // Use state machine to track position in sequence + generateMultiValueTransitionCode(coverpointp, binp, exprp, hitVarp, items); + } + } + + // Recursive helper to generate Cartesian product of cross bins + void generateCrossBinsRecursive(AstCoverCross* crossp, + const std::vector& coverpointRefs, + const std::vector>& allCpBins, + std::vector currentCombination, + size_t dimension) { + if (dimension == allCpBins.size()) { + // Base case: we have a complete combination, generate the cross bin + generateOneCrossBin(crossp, coverpointRefs, currentCombination); + return; + } + + // Recursive case: iterate through bins at current dimension + for (AstCoverBin* binp : allCpBins[dimension]) { + currentCombination.push_back(binp); + generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, currentCombination, + dimension + 1); + currentCombination.pop_back(); + } + } + + // Generate a single cross bin for a specific combination of bins + void generateOneCrossBin(AstCoverCross* crossp, + const std::vector& coverpointRefs, + const std::vector& bins) { + // Build sanitized name from all bins + string binName; + string varName = "__Vcov_" + crossp->name(); + + for (size_t i = 0; i < bins.size(); ++i) { + string sanitized = bins[i]->name(); + std::replace(sanitized.begin(), sanitized.end(), '[', '_'); + std::replace(sanitized.begin(), sanitized.end(), ']', '_'); + + if (i > 0) { + binName += "_x_"; + varName += "_x_"; + } + binName += sanitized; + varName += "_" + sanitized; + } + + // Create member variable for this cross bin + AstVar* const varp = new AstVar{crossp->fileline(), VVarType::MEMBER, varName, + bins[0]->findUInt32DType()}; + varp->isStatic(false); + m_covergroupp->addMembersp(varp); + + UINFO(4, " Created cross bin variable: " << varName << endl); + + // Track this for coverage computation + AstCoverBin* pseudoBinp = new AstCoverBin{crossp->fileline(), binName, + static_cast(nullptr), false, false}; + m_binInfos.push_back(BinInfo(pseudoBinp, varp, 1, nullptr, crossp)); + + // Generate matching code: if (bin1 && bin2 && ... && binN) varName++; + generateNWayCrossBinMatchCode(crossp, coverpointRefs, bins, varp); + } + + // Generate matching code for N-way cross bin + void generateNWayCrossBinMatchCode(AstCoverCross* crossp, + const std::vector& coverpointRefs, + const std::vector& bins, AstVar* hitVarp) { + UINFO(4, " Generating " << bins.size() << "-way cross bin match" << endl); + + // Build combined condition by ANDing all bin conditions + AstNodeExpr* fullCondp = nullptr; + + for (size_t i = 0; i < bins.size(); ++i) { + AstNodeExpr* exprp = coverpointRefs[i]->exprp(); + if (!exprp) continue; + + AstNodeExpr* condp = buildBinCondition(bins[i], exprp); + if (!condp) continue; + + if (fullCondp) { + fullCondp = new AstAnd{crossp->fileline(), fullCondp, condp}; + } else { + fullCondp = condp; + } + } + + if (!fullCondp) return; + + // Generate: if (cond1 && cond2 && ... && condN) { ++varName; } + AstNodeStmt* incrp = new AstAssign{ + crossp->fileline(), new AstVarRef{crossp->fileline(), hitVarp, VAccess::WRITE}, + new AstAdd{crossp->fileline(), + new AstVarRef{crossp->fileline(), hitVarp, VAccess::READ}, + new AstConst{crossp->fileline(), AstConst::WidthedValue{}, 32, 1}}}; + + AstIf* const ifp = new AstIf{crossp->fileline(), fullCondp, incrp}; + m_sampleFuncp->addStmtsp(ifp); + } + + void generateCrossCode(AstCoverCross* crossp) { + if (!m_sampleFuncp || !m_constructorp) { + crossp->v3warn(E_UNSUPPORTED, + "Cross coverage without sample() or constructor"); // LCOV_EXCL_LINE + return; + } + + UINFO(4, " Generating code for cross: " << crossp->name() << endl); + + // Resolve coverpoint references and build list + std::vector coverpointRefs; + AstNode* itemp = crossp->itemsp(); + while (itemp) { + AstNode* nextp = itemp->nextp(); + AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef); + if (refp) { + // Find the referenced coverpoint + AstCoverpoint* foundCpp = nullptr; + for (AstCoverpoint* cpp : m_coverpoints) { + if (cpp->name() == refp->name()) { + foundCpp = cpp; + break; + } + } + + if (!foundCpp) { + refp->v3warn(COVERIGN, + "Ignoring unsupported: cross references unknown coverpoint: " + + refp->name()); + // Don't delete crossp here - the caller's cleanup loop will delete it + return; + } + + coverpointRefs.push_back(foundCpp); + + // Delete the reference node - it's no longer needed + VL_DO_DANGLING(refp->unlinkFrBack()->deleteTree(), refp); + } + itemp = nextp; + } + + if (coverpointRefs.size() < 2) { + crossp->v3warn(E_UNSUPPORTED, + "Cross coverage requires at least 2 coverpoints"); // LCOV_EXCL_LINE + return; + } + + UINFO(4, " Generating " << coverpointRefs.size() << "-way cross" << endl); + + // Collect bins from all coverpoints (excluding ignore/illegal bins) + std::vector> allCpBins; + for (AstCoverpoint* cpp : coverpointRefs) { + std::vector cpBins; + for (AstNode* binp = cpp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_CAST(binp, CoverBin); + if (cbinp && cbinp->binsType() == VCoverBinsType::USER) { + cpBins.push_back(cbinp); + } + } + UINFO(4, " Found " << cpBins.size() << " bins in " << cpp->name() << endl); + allCpBins.push_back(cpBins); + } + + // Generate cross bins using Cartesian product + generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, {}, 0); + } + + AstNodeExpr* buildBinCondition(AstCoverBin* binp, AstNodeExpr* exprp) { + // Get the range list from the bin + AstNode* rangep = binp->rangesp(); + if (!rangep) return nullptr; + + // Check if this is a wildcard bin + bool isWildcard = (binp->binsType() == VCoverBinsType::BINS_WILDCARD); + + // Build condition by OR-ing all ranges together + AstNodeExpr* fullCondp = nullptr; + + for (AstNode* currRangep = rangep; currRangep; currRangep = currRangep->nextp()) { + AstNodeExpr* exprClonep = exprp->cloneTree(false); + AstNodeExpr* rangeCondp = nullptr; + + if (AstInsideRange* irp = VN_CAST(currRangep, InsideRange)) { + AstNode* minp = irp->lhsp(); + AstNode* maxp = irp->rhsp(); + + if (minp && maxp) { + AstNodeExpr* minExprp = VN_CAST(minp, NodeExpr); + AstNodeExpr* maxExprp = VN_CAST(maxp, NodeExpr); + if (minExprp && maxExprp) { + AstConst* minConstp = VN_CAST(minExprp, Const); + AstConst* maxConstp = VN_CAST(maxExprp, Const); + + if (minConstp && maxConstp && minConstp->toSInt() == maxConstp->toSInt()) { + // Single value + if (isWildcard) { + rangeCondp = buildWildcardCondition(binp, exprClonep, minConstp); + } else { + rangeCondp = new AstEq{binp->fileline(), exprClonep, + minExprp->cloneTree(false)}; + } + } else { + // Range - use signed comparisons if expression is signed + AstNodeExpr* gep; + AstNodeExpr* lep; + if (exprClonep->isSigned()) { + AstNodeExpr* const exprClone2p = exprp->cloneTree(false); + gep = new AstGteS{binp->fileline(), exprClonep, + minExprp->cloneTree(false)}; + lep = new AstLteS{binp->fileline(), exprClone2p, + maxExprp->cloneTree(false)}; + rangeCondp = new AstAnd{binp->fileline(), gep, lep}; + } else { + // For unsigned, skip >= 0 check as it's always true + AstConst* minConstp = VN_CAST(minExprp, Const); + AstConst* maxConstp = VN_CAST(maxExprp, Const); + const int exprWidth = exprClonep->widthMin(); + bool skipLowerCheck = (minConstp && minConstp->toUQuad() == 0); + bool skipUpperCheck = false; + if (maxConstp && exprWidth > 0 && exprWidth <= 64) { + const uint64_t maxVal = (exprWidth == 64) + ? ~static_cast(0) + : ((1ULL << exprWidth) - 1ULL); + skipUpperCheck = (maxConstp->toUQuad() == maxVal); + } + + if (skipLowerCheck && skipUpperCheck) { + rangeCondp + = new AstConst{binp->fileline(), AstConst::BitTrue{}}; + } else if (skipLowerCheck) { + // Only check upper bound for [0:max] + lep = new AstLte{binp->fileline(), exprClonep, + maxExprp->cloneTree(false)}; + rangeCondp = lep; + } else if (skipUpperCheck) { + // Only check lower bound when upper is maximal + gep = new AstGte{binp->fileline(), exprClonep, + minExprp->cloneTree(false)}; + rangeCondp = gep; + } else { + AstNodeExpr* const exprClone2p = exprp->cloneTree(false); + lep = new AstLte{binp->fileline(), exprClone2p, + maxExprp->cloneTree(false)}; + gep = new AstGte{binp->fileline(), exprClonep, + minExprp->cloneTree(false)}; + rangeCondp = new AstAnd{binp->fileline(), gep, lep}; + } + } + } + } + } + } else if (AstConst* constp = VN_CAST(currRangep, Const)) { + if (isWildcard) { + rangeCondp = buildWildcardCondition(binp, exprClonep, constp); + } else { + rangeCondp = new AstEq{binp->fileline(), exprClonep, constp->cloneTree(false)}; + } + } + + if (rangeCondp) { + fullCondp + = fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp; + } + } + + return fullCondp; + } + + // Build a wildcard condition: (expr & mask) == (value & mask) + // where mask has 1s for defined bits and 0s for wildcard bits + AstNodeExpr* buildWildcardCondition(AstCoverBin* binp, AstNodeExpr* exprp, AstConst* constp) { + FileLine* fl = binp->fileline(); + + // Extract mask from constant (bits that are not X/Z) + V3Number mask{constp, constp->width()}; + V3Number value{constp, constp->width()}; + + for (int bit = 0; bit < constp->width(); ++bit) { + // If bit is X or Z (don't care), set mask bit to 0 + // Otherwise set to 1 and keep the value + if (constp->num().bitIs0(bit) || constp->num().bitIs1(bit)) { + mask.setBit(bit, 1); + value.setBit(bit, constp->num().bitIs1(bit) ? 1 : 0); + } else { + mask.setBit(bit, 0); + value.setBit(bit, 0); + } + } + + // Generate: (expr & mask) == (value & mask) + AstConst* maskConstp = new AstConst{fl, mask}; + AstConst* valueConstp = new AstConst{fl, value}; + + AstNodeExpr* exprMasked = new AstAnd{fl, exprp, maskConstp}; + AstNodeExpr* valueMasked = new AstAnd{fl, valueConstp, maskConstp->cloneTree(false)}; + + return new AstEq{fl, exprMasked, valueMasked}; + } + + void generateCoverageComputationCode() { + UINFO(4, " Generating coverage computation code" << endl); + + // Invalidate cache: addMembersp() calls in generateCoverpointCode/generateCrossCode + // have added new members since the last scan, so clear before re-querying. + m_memberMap.clear(); + + // Find get_coverage() and get_inst_coverage() methods + AstFunc* const getCoveragep + = VN_CAST(m_memberMap.findMember(m_covergroupp, "get_coverage"), Func); + AstFunc* const getInstCoveragep + = VN_CAST(m_memberMap.findMember(m_covergroupp, "get_inst_coverage"), Func); + + if (!getCoveragep || !getInstCoveragep) { + UINFO(4, " Warning: Could not find get_coverage methods" << endl); + return; + } + + // Even if there are no bins, we still need to generate the coverage methods + // Empty covergroups should return 100% coverage + if (m_binInfos.empty()) { + UINFO(4, " No bins found, will generate method to return 100%" << endl); + } else { + UINFO(6, " Found " << m_binInfos.size() << " bins for coverage" << endl); + } + + // Generate code for get_inst_coverage() + if (getInstCoveragep) { generateCoverageMethodBody(getInstCoveragep); } + + // Generate code for get_coverage() (type-level) + // NOTE: Full type-level coverage requires instance tracking infrastructure + // For now, return 0.0 as a placeholder + if (getCoveragep) { + AstVar* returnVarp = VN_AS(getCoveragep->fvarp(), Var); + if (returnVarp) { + // TODO: Implement proper type-level coverage aggregation + // This requires tracking all instances and averaging their coverage + // For now, return 0.0 + getCoveragep->addStmtsp(new AstAssign{ + getCoveragep->fileline(), + new AstVarRef{getCoveragep->fileline(), returnVarp, VAccess::WRITE}, + new AstConst{getCoveragep->fileline(), AstConst::RealDouble{}, 0.0}}); + UINFO(4, " Added placeholder get_coverage() (returns 0.0)" << endl); + } + } + } + + void generateCoverageMethodBody(AstFunc* funcp) { + FileLine* fl = funcp->fileline(); + + // Count total bins (excluding ignore_bins and illegal_bins) + int totalBins = 0; + for (const BinInfo& bi : m_binInfos) { + UINFO(6, " Bin: " << bi.binp->name() << " type=" << (int)bi.binp->binsType() + << " IGNORE=" << (int)VCoverBinsType::BINS_IGNORE + << " ILLEGAL=" << (int)VCoverBinsType::BINS_ILLEGAL << endl); + if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE + && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { + totalBins++; + } + } + + UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size() << endl); + + if (totalBins == 0) { + // No coverage to compute - return 100% + UINFO(4, " Empty covergroup, returning 100.0" << endl); + AstVar* returnVarp = VN_AS(funcp->fvarp(), Var); + + // Find and replace existing assignment to return variable + AstAssign* existingReturnAssign = nullptr; + for (AstNode* stmtp = funcp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (AstAssign* assignp = VN_CAST(stmtp, Assign)) { + if (AstVarRef* lhsVarRef = VN_CAST(assignp->lhsp(), VarRef)) { + if (lhsVarRef->varp() == returnVarp) { + existingReturnAssign = assignp; + break; + } + } + } + } + + if (existingReturnAssign) { + // Replace the RHS of existing assignment from 0 to 100.0 + AstNode* oldRhs = existingReturnAssign->rhsp(); + if (oldRhs) VL_DO_DANGLING(oldRhs->unlinkFrBack()->deleteTree(), oldRhs); + existingReturnAssign->rhsp(new AstConst{fl, AstConst::RealDouble{}, 100.0}); + UINFO(4, " Replaced return value assignment to 100.0" << endl); + } else if (returnVarp) { + // No existing assignment found, add one + AstAssign* assignp + = new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::RealDouble{}, 100.0}}; + funcp->addStmtsp(assignp); + UINFO(4, " Added assignment to return 100.0" << endl); + } + return; + } + + // Create local variable to count covered bins + AstVar* coveredCountp + = new AstVar{fl, VVarType::BLOCKTEMP, "__Vcovered_count", funcp->findUInt32DType()}; + coveredCountp->funcLocal(true); + funcp->addStmtsp(coveredCountp); + + // Initialize: covered_count = 0 + funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 0}}); + + // For each regular bin, if count > 0, increment covered_count + for (const BinInfo& bi : m_binInfos) { + // Skip ignore_bins and illegal_bins in coverage calculation + if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE + || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + continue; + } + + // if (bin_count >= at_least) covered_count++; + AstIf* ifp = new AstIf{ + fl, + new AstGte{fl, new AstVarRef{fl, bi.varp, VAccess::READ}, + new AstConst{fl, AstConst::WidthedValue{}, 32, + static_cast(bi.atLeast)}}, + new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE}, + new AstAdd{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}, + nullptr}; + funcp->addStmtsp(ifp); + } + + // Find the return variable + AstVar* returnVarp = VN_AS(funcp->fvarp(), Var); + if (!returnVarp) { + UINFO(4, " Warning: No return variable found in " << funcp->name() << endl); + return; + } + + // Calculate coverage: (covered_count / total_bins) * 100.0 + // return_var = (double)covered_count / (double)total_bins * 100.0 + + // Cast covered_count to real/double + AstNodeExpr* coveredReal + = new AstIToRD{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}}; + + // Create total bins as a double constant + AstNodeExpr* totalReal + = new AstConst{fl, AstConst::RealDouble{}, static_cast(totalBins)}; + + // Divide using AstDivD (double division that emits native /) + AstNodeExpr* divExpr = new AstDivD{fl, coveredReal, totalReal}; + + // Multiply by 100 using AstMulD (double multiplication that emits native *) + AstNodeExpr* hundredConst = new AstConst{fl, AstConst::RealDouble{}, 100.0}; + AstNodeExpr* coverageExpr = new AstMulD{fl, hundredConst, divExpr}; + + // Assign to return variable + funcp->addStmtsp( + new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, coverageExpr}); + + UINFO(6, " Added coverage computation to " << funcp->name() << " with " << totalBins + << " bins (excluding ignore/illegal)" + << endl); + } + + int countBins(AstCoverpoint* nodep) { + int count = 0; + for (AstNode* binp = nodep->binsp(); binp; binp = binp->nextp()) { count++; } + return count; + } + + void generateCoverageRegistration() { + // Generate VL_COVER_INSERT calls for each bin in the covergroup + // This registers the bins with the coverage database so they can be reported + + UINFO(4, " Generating coverage database registration for " << m_binInfos.size() << " bins" + << endl); + + if (m_binInfos.empty()) return; + + // We need to add the registration code to the constructor + // The registration should happen after member variables are initialized + if (!m_constructorp) { + m_covergroupp->v3warn( + E_UNSUPPORTED, + "Cannot generate coverage registration without constructor"); // LCOV_EXCL_LINE + return; + } + + // For each bin, generate a VL_COVER_INSERT call + // The calls use CCall nodes to invoke VL_COVER_INSERT macro + for (const BinInfo& binInfo : m_binInfos) { + AstVar* varp = binInfo.varp; + AstCoverBin* binp = binInfo.binp; + AstCoverpoint* coverpointp = binInfo.coverpointp; + AstCoverCross* crossp = binInfo.crossp; + + // Skip illegal and ignore bins - they don't count towards coverage + if (binp->binsType() == VCoverBinsType::BINS_IGNORE + || binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + continue; + } + + FileLine* fl = binp->fileline(); + + // Build hierarchical name: covergroup.coverpoint.bin or covergroup.cross.bin + std::string hierName = m_covergroupp->name(); + std::string binName = binp->name(); + + if (coverpointp) { + // Coverpoint bin: use coverpoint name or generate from expression + std::string cpName = coverpointp->name(); + if (cpName.empty()) { + // Generate name from expression + if (coverpointp->exprp()) { + cpName = coverpointp->exprp()->name(); + if (cpName.empty()) cpName = "cp"; + } else { + cpName = "cp"; + } + } + hierName += "." + cpName; + } else if (crossp) { + // Cross bin: use cross name + std::string crossName = crossp->name(); + if (crossName.empty()) crossName = "cross"; + hierName += "." + crossName; + } + hierName += "." + binName; + + // Generate: VL_COVER_INSERT(contextp, hier, &binVar, "page", "v_covergroup/...", ...) + + UINFO(6, " Registering bin: " << hierName << " -> " << varp->name() << endl); + + // Build the coverage insert as a C statement + // The variable reference needs to be &this->varname, where varname gets mangled to + // __PVT__varname Use "page" field with v_covergroup prefix so type is extracted + // correctly (consistent with code coverage) + std::string pageName = "v_covergroup/" + m_covergroupp->name(); + std::string insertCall = "VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), "; + insertCall += "\"" + hierName + "\", "; + insertCall += "&(this->__PVT__" + varp->name() + "), "; + insertCall += "\"page\", \"" + pageName + "\", "; + insertCall += "\"filename\", \"" + fl->filename() + "\", "; + insertCall += "\"lineno\", \"" + std::to_string(fl->lineno()) + "\", "; + insertCall += "\"column\", \"" + std::to_string(fl->firstColumn()) + "\", "; + insertCall += "\"bin\", \"" + binName + "\");"; + + // Create a statement node with the coverage insert call + AstCStmt* cstmtp = new AstCStmt{fl, insertCall}; + + // Add to constructor + m_constructorp->addStmtsp(cstmtp); + + UINFO(6, " Added VL_COVER_INSERT call to constructor" << endl); + } + } + + // VISITORS + void visit(AstClass* nodep) override { + UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup() + << endl); + if (nodep->isCovergroup()) { + VL_RESTORER(m_covergroupp); + m_covergroupp = nodep; + m_sampleFuncp = nullptr; + m_constructorp = nullptr; + m_coverpoints.clear(); + m_coverCrosses.clear(); + + // Extract and store the clocking event from AstCovergroup node + // The parser creates this node to preserve the event information + bool hasUnsupportedEvent = false; + for (AstNode* itemp = nodep->membersp(); itemp;) { + AstNode* nextp = itemp->nextp(); + if (AstCovergroup* const cgp = VN_CAST(itemp, Covergroup)) { + // Store the event in the global map for V3Active to retrieve later + if (cgp->eventp()) { + // Check if the clocking event references a member variable (unsupported) + // Clocking events should be on signals/nets, not class members + bool eventUnsupported = false; + for (AstNode* senp = cgp->eventp()->sensesp(); senp; + senp = senp->nextp()) { + if (AstSenItem* const senItemp = VN_CAST(senp, SenItem)) { + if (AstVarRef* const varrefp + = VN_CAST(senItemp->sensp(), VarRef)) { + if (varrefp->varp() && varrefp->varp()->isClassMember()) { + cgp->v3warn(COVERIGN, "Ignoring unsupported: covergroup " + "clocking event on member variable"); + eventUnsupported = true; + hasUnsupportedEvent = true; + break; + } + } + } + } + + if (!eventUnsupported) { + // Leave cgp in the class membersp so the SenTree stays + // linked in the AST. V3Active will find it via membersp, + // use the event, then delete the AstCovergroup itself. + UINFO(4, "Keeping covergroup event node for V3Active: " + << nodep->name() << endl); + itemp = nextp; + continue; + } + } + // Remove the AstCovergroup node - either unsupported event or no event + cgp->unlinkFrBack(); + VL_DO_DANGLING(cgp->deleteTree(), cgp); + } + itemp = nextp; + } + + // If covergroup has unsupported clocking event, skip processing it + // but still clean up coverpoints so they don't reach downstream passes + if (hasUnsupportedEvent) { + iterateChildren(nodep); + for (AstCoverpoint* cpp : m_coverpoints) { + cpp->unlinkFrBack(); + VL_DO_DANGLING(cpp->deleteTree(), cpp); + } + for (AstCoverCross* crossp : m_coverCrosses) { + crossp->unlinkFrBack(); + VL_DO_DANGLING(crossp->deleteTree(), crossp); + } + return; + } + + // Find the sample() method and constructor + m_sampleFuncp = VN_CAST(m_memberMap.findMember(nodep, "sample"), Func); + m_constructorp = VN_CAST(m_memberMap.findMember(nodep, "new"), Func); + UINFO(9, "Found sample() method: " << (m_sampleFuncp ? "yes" : "no") << endl); + UINFO(9, "Found constructor: " << (m_constructorp ? "yes" : "no") << endl); + + iterateChildren(nodep); + processCovergroup(); + // Remove lowered coverpoints/crosses from the class - they have been + // fully translated into C++ code and must not reach downstream passes + for (AstCoverpoint* cpp : m_coverpoints) { + cpp->unlinkFrBack(); + VL_DO_DANGLING(cpp->deleteTree(), cpp); + } + for (AstCoverCross* crossp : m_coverCrosses) { + crossp->unlinkFrBack(); + VL_DO_DANGLING(crossp->deleteTree(), crossp); + } + } else { + iterateChildren(nodep); + } + } + + void visit(AstCoverpoint* nodep) override { + UINFO(9, "Found coverpoint: " << nodep->name() << endl); + m_coverpoints.push_back(nodep); + iterateChildren(nodep); + } + + void visit(AstCoverCross* nodep) override { + UINFO(9, "Found cross: " << nodep->name() << endl); + m_coverCrosses.push_back(nodep); + iterateChildren(nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit FunctionalCoverageVisitor(AstNetlist* nodep) { iterate(nodep); } + ~FunctionalCoverageVisitor() override = default; +}; + +//###################################################################### +// Functional coverage class functions + +void V3Covergroup::covergroup(AstNetlist* nodep) { + UINFO(4, __FUNCTION__ << ": " << endl); + { FunctionalCoverageVisitor{nodep}; } // Destruct before checking + V3Global::dumpCheckGlobalTree("coveragefunc", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3Covergroup.h b/src/V3Covergroup.h new file mode 100644 index 000000000..949b1484b --- /dev/null +++ b/src/V3Covergroup.h @@ -0,0 +1,30 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Covergroup implementation +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3COVERGROUP_H_ +#define VERILATOR_V3COVERGROUP_H_ + +#include "V3Ast.h" +#include "V3Error.h" + +//============================================================================ + +class V3Covergroup final { +public: + static void covergroup(AstNetlist* nodep); +}; + +#endif // Guard diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 904da3186..0c31798ec 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -227,6 +227,12 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { iterateAndNextConstNull(nodep->rhsp()); if (!m_suppressSemi) puts(";\n"); } + void visit(AstAssignDly* nodep) override { + iterateAndNextConstNull(nodep->lhsp()); + putfs(nodep, " <= "); + iterateAndNextConstNull(nodep->rhsp()); + puts(";\n"); + } void visit(AstAlias* nodep) override { putbs("alias "); iterateConst(nodep->itemsp()); @@ -267,7 +273,6 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { if (nodep->sensp()) puts(" "); iterateChildrenConst(nodep); } - void visit(AstCReset* nodep) override { puts("/*CRESET*/"); } void visit(AstCase* nodep) override { putfs(nodep, ""); if (nodep->priorityPragma()) puts("priority "); diff --git a/src/V3Global.h b/src/V3Global.h index 245cc05db..7e395eb31 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -130,6 +130,7 @@ class V3Global final { bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted bool m_useParallelBuild = false; // Use parallel build for model bool m_useRandSequence = false; // Has `randsequence` + bool m_useCovergroup = false; // Has covergroup declarations bool m_useRandomizeMethods = false; // Need to define randomize() class methods uint64_t m_currentHierBlockCost = 0; // Total cost of this hier block, used for scheduling @@ -213,6 +214,8 @@ public: void useParallelBuild(bool flag) { m_useParallelBuild = flag; } bool useRandSequence() const { return m_useRandSequence; } void useRandSequence(bool flag) { m_useRandSequence = flag; } + bool useCovergroup() const { return m_useCovergroup; } + void useCovergroup(bool flag) { m_useCovergroup = flag; } bool useRandomizeMethods() const { return m_useRandomizeMethods; } void useRandomizeMethods(bool flag) { m_useRandomizeMethods = flag; } void saveJsonPtrFieldName(const std::string& fieldName); diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index cf34778a9..ea3cff1ee 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -308,7 +308,6 @@ class LinkIncVisitor final : public VNVisitor { AstVar* const varp = new AstVar{ fl, VVarType::BLOCKTEMP, name, VFlagChildDType{}, new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, readp->cloneTree(true)}}; - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); if (m_ftaskp) varp->funcLocal(true); // Declare the variable diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 4e238c75f..fca4725dd 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1107,6 +1107,216 @@ class LinkParseVisitor final : public VNVisitor { iterateChildren(nodep); } + // Create boilerplate covergroup methods on the given AstClass. + // argsp/sampleArgsp are the raw arg lists still owned by the caller; they are iterated + // (cloned) but not deleted here. + static void createCovergroupMethods(AstClass* nodep, AstNode* argsp, AstNode* sampleArgsp) { + // Hidden static to take unspecified reference argument results + AstVar* const defaultVarp + = new AstVar{nodep->fileline(), VVarType::MEMBER, "__Vint", nodep->findIntDType()}; + defaultVarp->lifetime(VLifetime::STATIC_EXPLICIT); + nodep->addStmtsp(defaultVarp); + + // Handle constructor arguments - add function parameters and assignments + if (argsp) { + // Find the 'new' function to add parameters to + AstFunc* newFuncp = nullptr; + for (AstNode* memberp = nodep->membersp(); memberp; memberp = memberp->nextp()) { + if (AstFunc* const funcp = VN_CAST(memberp, Func)) { + if (funcp->name() == "new") { + newFuncp = funcp; + break; + } + } + } + if (newFuncp) { + // Save the existing body statements and unlink them + AstNode* const existingBodyp = newFuncp->stmtsp(); + if (existingBodyp) existingBodyp->unlinkFrBackWithNext(); + // Add function parameters and assignments + for (AstNode* argp = argsp; argp; argp = argp->nextp()) { + if (AstVar* const origVarp = VN_CAST(argp, Var)) { + AstVar* const paramp = origVarp->cloneTree(false); + paramp->funcLocal(true); + paramp->direction(VDirection::INPUT); + newFuncp->addStmtsp(paramp); + AstNodeExpr* const lhsp + = new AstParseRef{origVarp->fileline(), origVarp->name()}; + AstNodeExpr* const rhsp + = new AstParseRef{paramp->fileline(), paramp->name()}; + newFuncp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); + } + } + if (existingBodyp) newFuncp->addStmtsp(existingBodyp); + } + } + + // IEEE: option + { + v3Global.setUsesStdPackage(); + AstVar* const varp + = new AstVar{nodep->fileline(), VVarType::MEMBER, "option", VFlagChildDType{}, + new AstRefDType{nodep->fileline(), "vl_covergroup_options_t", + new AstClassOrPackageRef{nodep->fileline(), "std", + nullptr, nullptr}, + nullptr}}; + nodep->addMembersp(varp); + } + + // IEEE: type_option + { + v3Global.setUsesStdPackage(); + AstVar* const varp + = new AstVar{nodep->fileline(), VVarType::MEMBER, "type_option", VFlagChildDType{}, + new AstRefDType{nodep->fileline(), "vl_covergroup_type_options_t", + new AstClassOrPackageRef{nodep->fileline(), "std", + nullptr, nullptr}, + nullptr}}; + nodep->addMembersp(varp); + } + + // IEEE: function void sample([arguments]) + { + AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr}; + if (sampleArgsp) { + for (AstNode* argp = sampleArgsp; argp; argp = argp->nextp()) { + if (AstVar* const origVarp = VN_CAST(argp, Var)) { + AstVar* const paramp = origVarp->cloneTree(false); + paramp->funcLocal(true); + paramp->direction(VDirection::INPUT); + funcp->addStmtsp(paramp); + AstNodeExpr* const lhsp + = new AstParseRef{origVarp->fileline(), origVarp->name()}; + AstNodeExpr* const rhsp + = new AstParseRef{paramp->fileline(), paramp->name()}; + funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); + } + } + } + funcp->classMethod(true); + funcp->dtypep(funcp->findVoidDType()); + nodep->addMembersp(funcp); + } + + // IEEE: function void start(), void stop() + for (const string& name : {"start"s, "stop"s}) { + AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr}; + funcp->classMethod(true); + funcp->dtypep(funcp->findVoidDType()); + nodep->addMembersp(funcp); + } + + // IEEE: static function real get_coverage(optional ref int, optional ref int) + // IEEE: function real get_inst_coverage(optional ref int, optional ref int) + for (const string& name : {"get_coverage"s, "get_inst_coverage"s}) { + AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr}; + funcp->fileline()->warnOff(V3ErrorCode::NORETURN, true); + funcp->isStatic(name == "get_coverage"); + funcp->classMethod(true); + funcp->dtypep(funcp->findVoidDType()); + nodep->addMembersp(funcp); + { + AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, name, + nodep->findDoubleDType()}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + varp->funcLocal(true); + varp->direction(VDirection::OUTPUT); + varp->funcReturn(true); + funcp->fvarp(varp); + } + for (const string& varname : {"covered_bins"s, "total_bins"s}) { + AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, varname, + nodep->findStringDType()}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + varp->funcLocal(true); + varp->direction(VDirection::INPUT); + varp->valuep(new AstVarRef{nodep->fileline(), defaultVarp, VAccess::READ}); + funcp->addStmtsp(varp); + } + } + + // IEEE: function void set_inst_name(string) + { + AstFunc* const funcp + = new AstFunc{nodep->fileline(), "set_inst_name", nullptr, nullptr}; + funcp->classMethod(true); + funcp->dtypep(funcp->findVoidDType()); + nodep->addMembersp(funcp); + AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, "name", + nodep->findStringDType()}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + varp->funcLocal(true); + varp->direction(VDirection::INPUT); + funcp->addStmtsp(varp); + } + } + + void visit(AstCovergroup* nodep) override { + // If we're already inside a covergroup class, this is the sentinel AstCovergroup + // node carrying the clocking event for V3Covergroup - don't re-transform it. + if (m_modp && VN_IS(m_modp, Class) && VN_CAST(m_modp, Class)->isCovergroup()) return; + + // Transform raw parse-time AstCovergroup into a fully-formed AstClass + cleanFileline(nodep); + + const string libname = m_modp ? m_modp->libname() : ""; + AstClass* const cgClassp = new AstClass{nodep->fileline(), nodep->name(), libname}; + cgClassp->isCovergroup(true); + v3Global.useCovergroup(true); + + // Clocking event: unlink before deleteTree, attach as AstCovergroup child on class + if (AstSenTree* const eventp = nodep->eventp()) { + eventp->unlinkFrBack(); + AstCovergroup* const cgNodep = new AstCovergroup{ + nodep->fileline(), nodep->name(), nullptr, nullptr, nullptr, eventp}; + cgClassp->addMembersp(cgNodep); + } + + // Convert constructor args to member variables + for (AstNode* argp = nodep->argsp(); argp; argp = argp->nextp()) { + if (AstVar* const origVarp = VN_CAST(argp, Var)) { + AstVar* const memberp = origVarp->cloneTree(false); + memberp->varType(VVarType::MEMBER); + memberp->funcLocal(false); + memberp->direction(VDirection::NONE); + cgClassp->addMembersp(memberp); + } + } + + // Convert sample args to member variables + for (AstNode* argp = nodep->sampleArgsp(); argp; argp = argp->nextp()) { + if (AstVar* const origVarp = VN_CAST(argp, Var)) { + AstVar* const memberp = origVarp->cloneTree(false); + memberp->varType(VVarType::MEMBER); + memberp->funcLocal(false); + memberp->direction(VDirection::NONE); + cgClassp->addMembersp(memberp); + } + } + + // Create the constructor; detach membersp (coverage body) and use as its body + { + AstFunc* const newp = new AstFunc{nodep->fileline(), "new", nullptr, nullptr}; + newp->fileline()->warnOff(V3ErrorCode::NORETURN, true); + newp->classMethod(true); + newp->isConstructor(true); + newp->dtypep(cgClassp->dtypep()); + if (AstNode* const bodyp = nodep->membersp()) { + bodyp->unlinkFrBackWithNext(); + newp->addStmtsp(bodyp); + } + cgClassp->addMembersp(newp); + } + + // Add all boilerplate covergroup methods (reads argsp/sampleArgsp from nodep) + createCovergroupMethods(cgClassp, nodep->argsp(), nodep->sampleArgsp()); + + // Replace AstCovergroup with AstClass and process the new class normally + nodep->replaceWith(cgClassp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(cgClassp); + } + void visit(AstNode* nodep) override { // Default: Just iterate cleanFileline(nodep); diff --git a/src/V3MergeCond.cpp b/src/V3MergeCond.cpp index 6cddb01e7..0ed6f4cd9 100644 --- a/src/V3MergeCond.cpp +++ b/src/V3MergeCond.cpp @@ -250,6 +250,10 @@ class CodeMotionAnalysisVisitor final : public VNVisitorConst { void analyzeVarRef(AstVarRef* nodep) { const VAccess access = nodep->access(); AstVar* const varp = nodep->varp(); + // Add null check - varp can be null in some contexts (e.g., SenTree VarRefs) + if (!varp) return; + // Skip if not in a statement context (m_propsp can be null) + if (!m_propsp) return; // Gather read and written variables if (access.isReadOrRW()) m_propsp->m_rdVars.insert(varp); if (access.isWriteOrRW()) m_propsp->m_wrVars.insert(varp); @@ -295,7 +299,6 @@ class CodeMotionAnalysisVisitor final : public VNVisitorConst { iterateChildrenConst(nodep); } - // VISITORS void visit(AstNode* nodep) override { // Push a new stack entry at the start of a list, but only if the list is not a // single element (this saves a lot of allocations in expressions) diff --git a/src/V3OrderGraphBuilder.cpp b/src/V3OrderGraphBuilder.cpp index 1f4936e87..01aff38fb 100644 --- a/src/V3OrderGraphBuilder.cpp +++ b/src/V3OrderGraphBuilder.cpp @@ -331,6 +331,16 @@ class OrderGraphBuilder final : public VNVisitor { void visit(AstCoverToggle* nodep) override { // iterateLogic(nodep); } + void visit(AstStmtExpr* nodep) override { + // StmtExpr wraps expressions used as statements (e.g., method calls). + // If it's under an AstActive but not already in a logic context, treat it as logic. + // Otherwise just iterate normally. + if (!m_logicVxp && m_domainp) { + iterateLogic(nodep); + } else { + iterateChildren(nodep); + } + } //--- Ignored nodes void visit(AstVar*) override {} diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index 373402133..fa3110ada 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -15,11 +15,13 @@ //************************************************************************* #include "V3Ast.h" +#include "V3Const.h" #include "V3Control.h" #include "V3Global.h" #include "V3ParseImp.h" // Defines YYTYPE; before including bison header #include +#include class V3ParseGrammar final { public: @@ -92,96 +94,65 @@ public: nodep->trace(singletonp()->allTracingOn(fileline)); return nodep; } - void createCoverGroupMethods(AstClass* nodep, AstNode* sampleArgs) { - // Hidden static to take unspecified reference argument results - AstVar* const defaultVarp - = new AstVar{nodep->fileline(), VVarType::MEMBER, "__Vint", nodep->findIntDType()}; - defaultVarp->lifetime(VLifetime::STATIC_EXPLICIT); - nodep->addStmtsp(defaultVarp); + // Helper to move bins from parser list to coverpoint + void addCoverpointBins(AstCoverpoint* cp, AstNode* binsList) { + if (!binsList) return; - // IEEE: option - { - v3Global.setUsesStdPackage(); - AstVar* const varp - = new AstVar{nodep->fileline(), VVarType::MEMBER, "option", VFlagChildDType{}, - new AstRefDType{nodep->fileline(), "vl_covergroup_options_t", - new AstClassOrPackageRef{nodep->fileline(), "std", - nullptr, nullptr}, - nullptr}}; - nodep->addMembersp(varp); - } + // CRITICAL FIX: The parser creates a linked list of bins. When we try to move them + // to the coverpoint one by one while they're still linked, the addNext() logic + // that updates headtailp pointers creates circular references. We must fully + // unlink ALL bins before adding ANY to the coverpoint. + std::vector bins; + std::vector options; - // IEEE: type_option - { - v3Global.setUsesStdPackage(); - AstVar* const varp - = new AstVar{nodep->fileline(), VVarType::MEMBER, "type_option", VFlagChildDType{}, - new AstRefDType{nodep->fileline(), "vl_covergroup_type_options_t", - new AstClassOrPackageRef{nodep->fileline(), "std", - nullptr, nullptr}, - nullptr}}; - nodep->addMembersp(varp); - } + // To unlink the head node (which has no backp), create a temporary parent + AstBegin* tempParent = new AstBegin{binsList->fileline(), "[TEMP]", nullptr, true}; + tempParent->addStmtsp(binsList); // Now binsList has a backp - // IEEE: function void sample() - { - AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr}; - funcp->addStmtsp(sampleArgs); - funcp->classMethod(true); - funcp->dtypep(funcp->findVoidDType()); - nodep->addMembersp(funcp); - } + // Now unlink all bins - they all have backp now + for (AstNode *binp = binsList, *nextp; binp; binp = nextp) { + nextp = binp->nextp(); - // IEEE: function void start(), void stop() - for (const string& name : {"start"s, "stop"s}) { - AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr}; - funcp->classMethod(true); - funcp->dtypep(funcp->findVoidDType()); - nodep->addMembersp(funcp); - } - - // IEEE: static function real get_coverage(optional ref int, optional ref int) - // IEEE: function real get_inst_coverage(optional ref int, optional ref int) - for (const string& name : {"get_coverage"s, "get_inst_coverage"s}) { - AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr}; - funcp->fileline()->warnOff(V3ErrorCode::NORETURN, true); - funcp->isStatic(name == "get_coverage"); - funcp->classMethod(true); - funcp->dtypep(funcp->findVoidDType()); - nodep->addMembersp(funcp); - { - AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, name, - nodep->findDoubleDType()}; - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - varp->funcLocal(true); - varp->direction(VDirection::OUTPUT); - varp->funcReturn(true); - funcp->fvarp(varp); - } - for (const string& varname : {"covered_bins"s, "total_bins"s}) { - AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, varname, - nodep->findStringDType()}; - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - varp->funcLocal(true); - varp->direction(VDirection::INPUT); - varp->valuep(new AstVarRef{nodep->fileline(), defaultVarp, VAccess::READ}); - funcp->addStmtsp(varp); + if (AstCoverBin* cbinp = VN_CAST(binp, CoverBin)) { + cbinp->unlinkFrBack(); // Now this works for all bins including head + bins.push_back(cbinp); + } else if (AstCgOptionAssign* optp = VN_CAST(binp, CgOptionAssign)) { + optp->unlinkFrBack(); + // Convert AstCgOptionAssign to AstCoverOption + VCoverOptionType optType = VCoverOptionType::COMMENT; // default + if (optp->name() == "at_least") { + optType = VCoverOptionType::AT_LEAST; + } else if (optp->name() == "weight") { + optType = VCoverOptionType::WEIGHT; + } else if (optp->name() == "goal") { + optType = VCoverOptionType::GOAL; + } else if (optp->name() == "auto_bin_max") { + optType = VCoverOptionType::AUTO_BIN_MAX; + } else if (optp->name() == "per_instance") { + optType = VCoverOptionType::PER_INSTANCE; + } else if (optp->name() == "comment") { + optType = VCoverOptionType::COMMENT; + } else { + optp->v3warn(COVERIGN, + "Ignoring unsupported coverage option: " + optp->name()); + } + AstCoverOption* coverOptp = new AstCoverOption{optp->fileline(), optType, + optp->valuep()->cloneTree(false)}; + options.push_back(coverOptp); + VL_DO_DANGLING(optp->deleteTree(), optp); + } else { + binp->v3warn(COVERIGN, + "Unexpected node in bins list, ignoring"); // LCOV_EXCL_LINE + VL_DO_DANGLING(binp->deleteTree(), binp); } } - // IEEE: function void set_inst_name(string) - { - AstFunc* const funcp - = new AstFunc{nodep->fileline(), "set_inst_name", nullptr, nullptr}; - funcp->classMethod(true); - funcp->dtypep(funcp->findVoidDType()); - nodep->addMembersp(funcp); - AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, "name", - nodep->findStringDType()}; - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - varp->funcLocal(true); - varp->direction(VDirection::INPUT); - funcp->addStmtsp(varp); - } + + // Delete the temporary parent + VL_DO_DANGLING(tempParent->deleteTree(), tempParent); + + // Now add standalone bins and options to coverpoint + for (AstCoverBin* cbinp : bins) { cp->addBinsp(cbinp); } + for (AstCoverOption* optp : options) { cp->addOptionsp(optp); } } AstDisplay* createDisplayError(FileLine* fileline) { AstDisplay* nodep = new AstDisplay{fileline, VDisplayType::DT_ERROR, "", nullptr, nullptr}; diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp index 1de20a5da..7a5a9562a 100644 --- a/src/V3SchedPartition.cpp +++ b/src/V3SchedPartition.cpp @@ -240,6 +240,9 @@ class SchedGraphBuilder final : public VNVisitor { void visit(AstNodeProcedure* nodep) override { visitLogic(nodep); } void visit(AstNodeAssign* nodep) override { visitLogic(nodep); } void visit(AstCoverToggle* nodep) override { visitLogic(nodep); } + void visit(AstStmtExpr* nodep) override { + visitLogic(nodep); + } // Handle statement expressions like method calls // Pre and Post logic are handled separately void visit(AstAlwaysPre* nodep) override {} diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index b723cbcb9..4ee8f1801 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -343,11 +343,30 @@ class TimingSuspendableVisitor final : public VNVisitor { } } void visit(AstNodeCCall* nodep) override { - new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()), getSuspendDepVtx(m_procp), - P_CALL}; + // Skip automatic covergroup sampling calls (marked with user3==1) + if (nodep->user3()) { + iterateChildren(nodep); + return; + } - new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()), - getNeedsProcDepVtx(m_procp), P_CALL}; + AstCFunc* funcp = nodep->funcp(); + if (!funcp) { + iterateChildren(nodep); + return; + } + + // Skip if we're not inside a function/procedure (m_procp would be null) + // This can happen for calls in Active nodes at module scope + if (!m_procp) { + iterateChildren(nodep); + return; + } + + UINFO(9, "V3Timing: Processing CCall to " << funcp->name() << " in dependency graph\n"); + new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(funcp), getSuspendDepVtx(m_procp), P_CALL}; + + new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(funcp), getNeedsProcDepVtx(m_procp), + P_CALL}; iterateChildren(nodep); } @@ -914,8 +933,16 @@ class TimingControlVisitor final : public VNVisitor { } } void visit(AstNodeCCall* nodep) override { - if (nodep->funcp()->needProcess()) m_hasProcess = true; - if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable + AstCFunc* const funcp = nodep->funcp(); + + // Skip automatic covergroup sampling calls + if (funcp->isCovergroupSample()) { + iterateChildren(nodep); + return; + } + + if (funcp->needProcess()) m_hasProcess = true; + if (hasFlags(funcp, T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable // Calls to suspendables are always void return type, hence parent must be StmtExpr AstStmtExpr* const stmtp = VN_AS(nodep->backp(), StmtExpr); stmtp->replaceWith(new AstCAwait{nodep->fileline(), nodep->unlinkFrBack()}); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 06415443a..f01f5b811 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1699,8 +1699,32 @@ class WidthVisitor final : public VNVisitor { if (m_vup->prelim()) iterateCheckSizedSelf(nodep, "LHS", nodep->lhsp(), SELF, BOTH); } void visit(AstCgOptionAssign* nodep) override { - // We report COVERIGN on the whole covergroup; if get more fine-grained add this - // nodep->v3warn(COVERIGN, "Ignoring unsupported: coverage option"); + // Extract covergroup option values and store in AstClass before deleting + // Find parent covergroup (AstClass with isCovergroup() == true) + AstClass* cgClassp = nullptr; + for (AstNode* parentp = nodep->backp(); parentp; parentp = parentp->backp()) { + if (AstClass* classp = VN_CAST(parentp, Class)) { + if (classp->isCovergroup()) { + cgClassp = classp; + break; + } + } + } + + if (cgClassp) { + // Process supported options + if (nodep->name() == "auto_bin_max" && !nodep->typeOption()) { + // Extract constant value + if (AstConst* constp = VN_CAST(nodep->valuep(), Const)) { + cgClassp->cgAutoBinMax(constp->toSInt()); + UINFO(6, " Covergroup " << cgClassp->name() << " option.auto_bin_max = " + << constp->toSInt() << endl); + } + } + // Add more options here as needed (weight, goal, at_least, per_instance, comment) + } + + // Delete the assignment node (we've extracted the value) VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstPow* nodep) override { @@ -3320,6 +3344,9 @@ class WidthVisitor final : public VNVisitor { } void visit(AstInsideRange* nodep) override { // Just do each side; AstInside will rip these nodes out later + // Constant-fold range bounds (e.g., NEGATE(100) becomes -100) + V3Const::constifyParamsEdit(nodep->lhsp()); // May relink pointed to node + V3Const::constifyParamsEdit(nodep->rhsp()); // May relink pointed to node userIterateAndNext(nodep->lhsp(), m_vup); userIterateAndNext(nodep->rhsp(), m_vup); nodep->dtypeFrom(nodep->lhsp()); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 1d8c523f8..df393a7f0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -38,6 +38,7 @@ #include "V3Control.h" #include "V3Coverage.h" #include "V3CoverageJoin.h" +#include "V3Covergroup.h" #include "V3Dead.h" #include "V3Delayed.h" #include "V3Depth.h" @@ -156,7 +157,10 @@ static void process() { } // Convert parseref's to varrefs, and other directly post parsing fixups + // Note: must run before removeStd() as it may create std:: references (e.g. covergroups) V3LinkParse::linkParse(v3Global.rootp()); + // Remove std package if unused (must be after V3LinkParse which may set usesStdPackage) + v3Global.removeStd(); // Cross-link signal names // Cross-link dotted hierarchical references V3LinkDot::linkDotPrimary(v3Global.rootp()); @@ -230,6 +234,10 @@ static void process() { // Before we do dead code elimination and inlining, or we'll lose it. if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp()); + // Functional coverage code generation + // Generate code for covergroups/coverpoints + if (v3Global.useCovergroup()) V3Covergroup::covergroup(v3Global.rootp()); + // Resolve randsequence if they are used by the design if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp()); @@ -737,7 +745,6 @@ static bool verilate(const string& argString) { // Read first filename v3Global.readFiles(); - v3Global.removeStd(); // Link, etc, if needed if (!v3Global.opt.preprocOnly()) { // diff --git a/src/verilog.y b/src/verilog.y index 76ca9f54e..a35e28153 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6895,40 +6895,23 @@ covergroup_declaration: // ==IEEE: covergroup_declaration yCOVERGROUP idAny cgPortListE coverage_eventE ';' /*cont*/ coverage_spec_or_optionListE /*cont*/ yENDGROUP endLabelE - { AstClass *cgClassp = new AstClass{$2, *$2, PARSEP->libname()}; - cgClassp->isCovergroup(true); - AstFunc* const newp = new AstFunc{$1, "new", nullptr, nullptr}; - newp->fileline()->warnOff(V3ErrorCode::NORETURN, true); - newp->classMethod(true); - newp->isConstructor(true); - newp->dtypep(cgClassp->dtypep()); - newp->addStmtsp($3); - newp->addStmtsp($6); - cgClassp->addMembersp(newp); - GRAMMARP->createCoverGroupMethods(cgClassp, $4); - - $$ = cgClassp; - GRAMMARP->endLabel($8, $$, $8); - BBCOVERIGN($1, "Ignoring unsupported: covergroup"); - } + { AstSenTree* clockp = nullptr; + AstNode* sampleArgsp = nullptr; + if ($4) { + if (VN_IS($4, SenItem)) + clockp = new AstSenTree{$1, VN_AS($4, SenItem)}; + else + sampleArgsp = $4; + } + $$ = new AstCovergroup{$1, *$2, static_cast($3), + static_cast(sampleArgsp), $6, clockp}; + GRAMMARP->endLabel($8, $$, $8); } | yCOVERGROUP yEXTENDS idAny ';' /*cont*/ coverage_spec_or_optionListE /*cont*/ yENDGROUP endLabelE - { AstClass *cgClassp = new AstClass{$3, *$3, PARSEP->libname()}; - cgClassp->isCovergroup(true); - AstFunc* const newp = new AstFunc{$1, "new", nullptr, nullptr}; - newp->fileline()->warnOff(V3ErrorCode::NORETURN, true); - newp->classMethod(true); - newp->isConstructor(true); - newp->dtypep(cgClassp->dtypep()); - newp->addStmtsp($5); - cgClassp->addMembersp(newp); - GRAMMARP->createCoverGroupMethods(cgClassp, nullptr); - - $$ = cgClassp; - GRAMMARP->endLabel($7, $$, $7); - BBCOVERIGN($1, "Ignoring unsupported: covergroup"); - } + { BBCOVERIGN($1, "Ignoring unsupported: covergroup inheritance (extends)"); + $$ = new AstCovergroup{$3, *$3, nullptr, nullptr, $5, nullptr}; + GRAMMARP->endLabel($7, $$, $7); } ; cgPortListE: @@ -6975,21 +6958,46 @@ coverage_option: // ==IEEE: coverage_option cover_point: // ==IEEE: cover_point // // [ [ data_type_or_implicit ] cover_point_identifier ':' ] yCOVERPOINT yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverpoint"); DEL($2, $3, $4); } + { AstCoverpoint* const cp = new AstCoverpoint{$1, "", $2}; + if ($3) cp->iffp(VN_AS($3, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $4); + $$ = cp; } // // IEEE-2012: class_scope before an ID | id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($3, "Ignoring unsupported: coverpoint"); DEL($4, $5, $6);} + { AstCoverpoint* const cp = new AstCoverpoint{$3, *$1, $4}; + if ($5) cp->iffp(VN_AS($5, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $6); + $$ = cp; } // // data_type_or_implicit expansion | data_type id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($4, "Ignoring unsupported: coverpoint"); DEL($1, $5, $6, $7);} + { AstCoverpoint* const cp = new AstCoverpoint{$4, *$2, $5}; + if ($6) cp->iffp(VN_AS($6, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $7); + $$ = cp; + DEL($1); } | yVAR data_type id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverpoint"); DEL($2, $6, $7, $8); } + { AstCoverpoint* const cp = new AstCoverpoint{$5, *$3, $6}; + if ($7) cp->iffp(VN_AS($7, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $8); + $$ = cp; + DEL($2); } | yVAR implicit_typeE id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverpoint"); DEL($2, $6, $7, $8); } + { AstCoverpoint* const cp = new AstCoverpoint{$5, *$3, $6}; + if ($7) cp->iffp(VN_AS($7, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $8); + $$ = cp; + DEL($2); } | signingE rangeList id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverpoint"); DEL($2, $6, $7, $8); } + { AstCoverpoint* const cp = new AstCoverpoint{$5, *$3, $6}; + if ($7) cp->iffp(VN_AS($7, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $8); + $$ = cp; + DEL($2); } | signing id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($4, "Ignoring unsupported: coverpoint"); DEL($5, $6, $7); } + { AstCoverpoint* const cp = new AstCoverpoint{$4, *$2, $5}; + if ($6) cp->iffp(VN_AS($6, NodeExpr)); + GRAMMARP->addCoverpointBins(cp, $7); + $$ = cp; } // // IEEE-2012: | bins_or_empty { $$ = $1; } ; @@ -6997,7 +7005,7 @@ cover_point: // ==IEEE: cover_point iffE: // IEEE: part of cover_point, others /* empty */ { $$ = nullptr; } | yIFF '(' expr ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover 'iff'"); DEL($3); } + { $$ = $3; /* Keep iff condition for coverpoint */ } ; bins_or_empty: // ==IEEE: bins_or_empty @@ -7021,55 +7029,127 @@ bins_or_options: // ==IEEE: bins_or_options // // Superset of IEEE - we allow []'s in more places coverage_option { $$ = $1; } // // Can't use wildcardE as results in conflicts - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { $$ = nullptr; BBCOVERIGN($4, "Ignoring unsupported: cover bin specification"); DEL($3, $6, $8); } - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($8, "Ignoring unsupported: cover bin 'with' specification"); DEL($3, $6, $10, $12); } - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'with' specification"); DEL($3, $8, $10); } - | yWILDCARD bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: cover bin 'wildcard' specification"); DEL($4, $7, $9); } - | yWILDCARD bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($9, "Ignoring unsupported: cover bin 'wildcard' 'with' specification"); DEL($4, $7, $11, $13); } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$2, *$2, $6, false, false}; + if ($3) VN_AS($$, CoverBin)->isArray(true); // If bins_orBraE returned non-null, it's array + DEL($8); } + | yBINS idAny/*bin_identifier*/ '[' cgexpr ']' iffE + { // Check for automatic bins: bins auto[N] + if (*$2 == "auto") { + $$ = new AstCoverBin{$2, *$2, $4}; + DEL($6); + } else { + $$ = nullptr; + BBCOVERIGN($2, "Ignoring unsupported: bin array (non-auto)"); + DEL($4, $6); + } + } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$2, *$2, $6, true, false}; + if ($3) VN_AS($$, CoverBin)->isArray(true); + DEL($8); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$2, *$2, $6, false, true}; + if ($3) VN_AS($$, CoverBin)->isArray(true); + DEL($8); } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = new AstCoverBin{$2, *$2, $6, false, false}; + DEL($10, $12); /* TODO: Support 'with' clause */ } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = new AstCoverBin{$2, *$2, $6, true, false}; + DEL($10, $12); /* TODO: Support 'with' clause */ } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = new AstCoverBin{$2, *$2, $6, false, true}; + DEL($10, $12); /* TODO: Support 'with' clause */ } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'with' specification"); DEL($8, $10); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'with' specification"); DEL($8, $10); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'with' specification"); DEL($8, $10); } + | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$3, *$3, $7, false, false, true}; + DEL($9); } + | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$3, *$3, $7, true, false, true}; + DEL($9); } + | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { $$ = new AstCoverBin{$3, *$3, $7, false, true, true}; + DEL($9); } + | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($9, "Ignoring unsupported: cover bin 'wildcard' 'with' specification"); DEL($7, $11, $13); } + | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($9, "Ignoring unsupported: cover bin 'wildcard' 'with' specification"); DEL($7, $11, $13); } + | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($9, "Ignoring unsupported: cover bin 'wildcard' 'with' specification"); DEL($7, $11, $13); } // // // cgexpr part of trans_list - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { $$ = nullptr; BBCOVERIGN($4, "Ignoring unsupported: cover bin trans list"); DEL($3, $5, $6); } - | yWILDCARD bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover bin 'wildcard' trans list"); DEL($4, $6, $7);} + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { FileLine* isArray = $3; + $$ = new AstCoverBin{$2, *$2, static_cast($5), false, false, isArray != nullptr}; + DEL($6); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { FileLine* isArray = $3; + $$ = new AstCoverBin{$2, *$2, static_cast($5), true, false, isArray != nullptr}; + DEL($6); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { FileLine* isArray = $3; + $$ = new AstCoverBin{$2, *$2, static_cast($5), false, true, isArray != nullptr}; + DEL($6); } + | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover bin 'wildcard' trans list"); DEL($6, $7);} + | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover bin 'wildcard' trans list"); DEL($6, $7);} + | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover bin 'wildcard' trans list"); DEL($6, $7);} // - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: cover bin 'default'"); DEL($3, $6); } - | bins_keyword idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE - { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'default' 'sequence'"); DEL($3, $7); } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE + { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::DEFAULT}; + DEL($6); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE + { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_IGNORE}; + DEL($6); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE + { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_ILLEGAL}; + DEL($6); } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'default' 'sequence'"); DEL($7); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'default' 'sequence'"); DEL($7); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE + { $$ = nullptr; BBCOVERIGN($6, "Ignoring unsupported: cover bin 'default' 'sequence'"); DEL($7); } ; -bins_orBraE: // IEEE: part of bins_or_options: +bins_orBraE: // IEEE: part of bins_or_options: returns fileline (abuse for boolean flag) /* empty */ { $$ = nullptr; } - | '[' ']' { $$ = nullptr; /*UNSUP*/ } + | '[' ']' { $$ = $1; /* Mark as array */ } | '[' cgexpr ']' { $$ = nullptr; /*UNSUP*/ DEL($2); } ; -bins_keyword: // ==IEEE: bins_keyword - yBINS { $$ = $1; /*UNSUP*/ } - | yILLEGAL_BINS { $$ = $1; /*UNSUP*/ } - | yIGNORE_BINS { $$ = $1; /*UNSUP*/ } - ; - trans_list: // ==IEEE: trans_list '(' trans_set ')' { $$ = $2; } | trans_list ',' '(' trans_set ')' { $$ = addNextNull($1, $4); } ; -trans_set: // ==IEEE: trans_set - trans_range_list { $$ = $1; } - // // Note the { => } in the grammar, this is really a list +trans_set: // ==IEEE: trans_set (returns AstCoverTransSet) + trans_range_list { + // Single transition item - wrap in AstCoverTransSet + $$ = new AstCoverTransSet{$1, static_cast($1)}; + } | trans_set yP_EQGT trans_range_list - { $$ = $1; BBCOVERIGN($2, "Ignoring unsupported: cover trans set '=>'"); DEL($3); } + { + // Chain transition items with => operator + // Add new item to existing set + $$ = $1; + static_cast($$)->addItemsp(static_cast($3)); + } ; -trans_range_list: // ==IEEE: trans_range_list - trans_item { $$ = $1; } +trans_range_list: // ==IEEE: trans_range_list (returns AstCoverTransItem) + trans_item { + // Simple transition item without repetition + $$ = new AstCoverTransItem{$1, $1, VTransRepType::NONE}; + } | trans_item yP_BRASTAR cgexpr ']' { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[*'"); DEL($1, $3); } | trans_item yP_BRASTAR cgexpr ':' cgexpr ']' @@ -7084,7 +7164,7 @@ trans_range_list: // ==IEEE: trans_range_list { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[='"); DEL($1, $3, $5); } ; -trans_item: // ==IEEE: range_list +trans_item: // ==IEEE: range_list (returns range list node) covergroup_range_list { $$ = $1; } ; @@ -7096,9 +7176,94 @@ covergroup_range_list: // ==IEEE: covergroup_range_list cover_cross: // ==IEEE: cover_cross id/*cover_point_identifier*/ ':' yCROSS list_of_cross_items iffE cross_body - { $$ = nullptr; BBCOVERIGN($3, "Ignoring unsupported: cover cross"); DEL($4, $5, $6); } + { + AstCoverCross* const nodep = new AstCoverCross{$3, *$1, + VN_AS($4, CoverpointRef)}; + if ($6) { // cross_body items (options, bins) + for (AstNode* itemp = $6; itemp; ) { + AstNode* const nextp = itemp->nextp(); + // Helper: unlink itemp from the standalone bison list. + // Head nodes have m_backp==nullptr; use nextp->unlinkFrBackWithNext() + // to detach the rest of the list so itemp->m_nextp becomes null. + const auto unlinkItem = [&]() { + if (itemp->backp()) { + itemp->unlinkFrBack(); + } else if (nextp) { + nextp->unlinkFrBackWithNext(); + } + }; + if (AstCoverOption* optp = VN_CAST(itemp, CoverOption)) { + unlinkItem(); + nodep->addOptionsp(optp); + } else if (AstCoverCrossBins* binp = VN_CAST(itemp, CoverCrossBins)) { + unlinkItem(); + nodep->addBinsp(binp); + } else if (VN_IS(itemp, CgOptionAssign)) { + unlinkItem(); + VL_DO_DANGLING(itemp->deleteTree(), itemp); + } else if (VN_IS(itemp, Func)) { + // Function declarations in cross bodies are unsupported + // Skip them - they will be deleted when bins expressions referencing + // them are deleted via DEL() in the cross_body_item rules + } else { + // Delete other unsupported items + unlinkItem(); + VL_DO_DANGLING(itemp->deleteTree(), itemp); + } + itemp = nextp; + } + } + if ($5) { + $5->v3warn(COVERIGN, "Ignoring unsupported: cross iff condition"); + VL_DO_DANGLING($5->deleteTree(), $5); + } + $$ = nodep; + } | yCROSS list_of_cross_items iffE cross_body - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: cover cross"); DEL($2, $3, $4); } + { + AstCoverCross* const nodep = new AstCoverCross{$1, + "__cross" + cvtToStr(GRAMMARP->s_typeImpNum++), + VN_AS($2, CoverpointRef)}; + if ($4) { // cross_body items (options, bins) + for (AstNode* itemp = $4; itemp; ) { + AstNode* const nextp = itemp->nextp(); + // Helper: unlink itemp from the standalone bison list. + // Head nodes have m_backp==nullptr; use nextp->unlinkFrBackWithNext() + // to detach the rest of the list so itemp->m_nextp becomes null. + const auto unlinkItem = [&]() { + if (itemp->backp()) { + itemp->unlinkFrBack(); + } else if (nextp) { + nextp->unlinkFrBackWithNext(); + } + }; + if (AstCoverOption* optp = VN_CAST(itemp, CoverOption)) { + unlinkItem(); + nodep->addOptionsp(optp); + } else if (AstCoverCrossBins* binp = VN_CAST(itemp, CoverCrossBins)) { + unlinkItem(); + nodep->addBinsp(binp); + } else if (VN_IS(itemp, CgOptionAssign)) { + unlinkItem(); + VL_DO_DANGLING(itemp->deleteTree(), itemp); + } else if (VN_IS(itemp, Func)) { + // Function declarations in cross bodies are unsupported + // Skip them - they will be deleted when bins expressions referencing + // them are deleted via DEL() in the cross_body_item rules + } else { + // Delete other unsupported items + unlinkItem(); + VL_DO_DANGLING(itemp->deleteTree(), itemp); + } + itemp = nextp; + } + } + if ($3) { + $3->v3warn(COVERIGN, "Ignoring unsupported: cross iff condition"); + VL_DO_DANGLING($3->deleteTree(), $3); + } + $$ = nodep; + } ; list_of_cross_items: // ==IEEE: list_of_cross_items @@ -7113,7 +7278,8 @@ cross_itemList: // IEEE: part of list_of_cross_items ; cross_item: // ==IEEE: cross_item - idDotted/*cover_point_identifier or variable_identifier*/ { $1->deleteTree(); $$ = nullptr; /*UNSUP*/ } + id/*cover_point_identifier*/ + { $$ = new AstCoverpointRef{$1, *$1}; } ; cross_body: // ==IEEE: cross_body @@ -7133,12 +7299,16 @@ cross_body_itemList: // IEEE: part of cross_body cross_body_item: // ==IEEE: cross_body_item function_declaration - { $$ = $1; BBCOVERIGN($1->fileline(), "Ignoring unsupported: coverage cross 'function' declaration"); } + { $$ = nullptr; BBCOVERIGN($1->fileline(), "Ignoring unsupported: coverage cross 'function' declaration"); DEL($1); } // // IEEE: bins_selection_or_option | coverage_option ';' { $$ = $1; } - // // IEEE: bins_selection - | bins_keyword idAny/*new-bin_identifier*/ '=' select_expression iffE ';' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage cross bin"); DEL($4, $5); } + // // IEEE: bins_selection - for now, we ignore explicit cross bins + | yBINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: explicit coverage cross bins"); DEL($4, $5); } + | yIGNORE_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: explicit coverage cross bins"); DEL($4, $5); } + | yILLEGAL_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' + { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: explicit coverage cross bins"); DEL($4, $5); } | error ';' { $$ = nullptr; } // LCOV_EXCL_LINE ; @@ -7159,7 +7329,7 @@ select_expression_r: | '!' yBINSOF '(' bins_expression ')' { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select expression 'binsof'"); DEL($4); } | yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverage select expression 'intersect'"); DEL($3, $7); } + { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverage select expression 'intersect'"); DEL($7); } | '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' { } { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverage select expression 'intersect'"); DEL($4, $8); } | yWITH__PAREN '(' cgexpr ')' @@ -7204,7 +7374,7 @@ bins_expression: // ==IEEE: bins_expression coverage_eventE: // IEEE: [ coverage_event ] /* empty */ { $$ = nullptr; } | clocking_event - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage clocking event"); DEL($1); } + { $$ = $1; } // Keep the clocking event for automatic sampling | yWITH__ETC yFUNCTION idAny/*"sample"*/ '(' tf_port_listE ')' { if (*$3 != "sample") { $3->v3error("Coverage sampling function must be named 'sample'"); diff --git a/test_regress/t/t_covergroup_array_bins.v b/test_regress/t/t_covergroup_array_bins.v new file mode 100644 index 000000000..b6f318bbd --- /dev/null +++ b/test_regress/t/t_covergroup_array_bins.v @@ -0,0 +1,87 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test array bins - separate bin per value + +module t; + /* verilator lint_off UNSIGNED */ + bit [7:0] data; + + covergroup cg; + coverpoint data { + // Array bins: creates 3 separate bins + bins values[] = {1, 5, 9}; + + // Non-array bin: creates 1 bin covering all values + bins grouped = {2, 6, 10}; + } + endgroup + + initial begin + cg cg_inst; + real cov; + + cg_inst = new(); + + // Initial coverage should be 0% + cov = cg_inst.get_inst_coverage(); + if (cov != 0.0) begin + $error("Expected 0%% coverage, got %0.2f%%", cov); + end + + // Hit first array bin value (1) + data = 1; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After hitting value 1: %0.2f%%", cov); + // 1 bin out of 4 total bins (3 array bins + 1 grouped bin) + if (cov < 23.0 || cov > 27.0) begin + $error("Expected ~25%% (1/4 bins), got %0.2f%%", cov); + end + + // Hit second array bin value (5) + data = 5; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After hitting value 5: %0.2f%%", cov); + // 2 bins out of 4 + if (cov < 48.0 || cov > 52.0) begin + $error("Expected ~50%% (2/4 bins), got %0.2f%%", cov); + end + + // Hit the grouped bin (covers all of 2, 6, 10) + data = 6; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After hitting grouped bin: %0.2f%%", cov); + // 3 bins out of 4 + if (cov < 73.0 || cov > 77.0) begin + $error("Expected ~75%% (3/4 bins), got %0.2f%%", cov); + end + + // Hit third array bin value (9) + data = 9; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After hitting value 9: %0.2f%%", cov); + // All 4 bins covered + if (cov != 100.0) begin + $error("Expected 100%% (4/4 bins), got %0.2f%%", cov); + end + + // Verify hitting other values in grouped bin doesn't increase coverage + data = 2; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + if (cov != 100.0) begin + $error("Coverage should stay 100%%, got %0.2f%%", cov); + end + + $display("Array bins test PASSED"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_auto_bins.py b/test_regress/t/t_covergroup_auto_bins.py new file mode 100755 index 000000000..e66ef82df --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bins.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Test automatic bins: bins auto[N] +# +# 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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_auto_bins.v b/test_regress/t/t_covergroup_auto_bins.v new file mode 100644 index 000000000..224310c59 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bins.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + /* verilator lint_off UNSIGNED */ + /* verilator lint_off CMPCONST */ + logic [2:0] data; // 3-bit: 0-7 + + covergroup cg; + coverpoint data { + bins auto[4]; // Should create 4 bins: [0:1], [2:3], [4:5], [6:7] + } + endgroup + /* verilator lint_on CMPCONST */ + + initial begin + automatic cg cg_inst = new; + + // Initial coverage should be 0% + $display("Coverage initial: %f%% (expected ~0.00%%)", cg_inst.get_inst_coverage()); + + // Sample first bin: 0 or 1 + data = 0; + cg_inst.sample(); + $display("Coverage after 0: %f%% (expected ~25.00%%)", cg_inst.get_inst_coverage()); + + // Sample second bin: 2 or 3 + data = 2; + cg_inst.sample(); + $display("Coverage after 2: %f%% (expected ~50.00%%)", cg_inst.get_inst_coverage()); + + // Sample third bin: 4 or 5 + data = 5; + cg_inst.sample(); + $display("Coverage after 5: %f%% (expected ~75.00%%)", cg_inst.get_inst_coverage()); + + // Sample fourth bin: 6 or 7 + data = 7; + cg_inst.sample(); + $display("Coverage complete: %f%% (expected ~100.00%%)", cg_inst.get_inst_coverage()); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_auto_sample.cpp b/test_regress/t/t_covergroup_auto_sample.cpp new file mode 100644 index 000000000..7dca61543 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample.cpp @@ -0,0 +1,28 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// Simple test harness for t_covergroup_auto_sample - provides clock +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +#include "verilated.h" + +#include "Vt_covergroup_auto_sample.h" + +int main(int argc, char** argv) { + Verilated::commandArgs(argc, argv); + Vt_covergroup_auto_sample* top = new Vt_covergroup_auto_sample; + + // Run for 20 cycles + for (int i = 0; i < 20; i++) { + top->clk = 0; + top->eval(); + top->clk = 1; + top->eval(); + + if (Verilated::gotFinish()) break; + } + + delete top; + return 0; +} diff --git a/test_regress/t/t_covergroup_auto_sample.py b/test_regress/t/t_covergroup_auto_sample.py new file mode 100755 index 000000000..c1943295f --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample.py @@ -0,0 +1,19 @@ +#!/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 automatic sampling with --no-timing (default) +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_auto_sample.v b/test_regress/t/t_covergroup_auto_sample.v new file mode 100644 index 000000000..848419dd1 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample.v @@ -0,0 +1,55 @@ +// DESCRIPTION: Verilator: Test automatic sampling with clocking events +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [1:0] data; + + // Covergroup with automatic sampling on posedge clk + covergroup cg @(posedge clk); + cp_data: coverpoint data { + bins zero = {2'b00}; + bins one = {2'b01}; + bins two = {2'b10}; + bins three = {2'b11}; + } + endgroup + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: data <= 2'b00; // Hit bin zero + 1: data <= 2'b01; // Hit bin one + 2: data <= 2'b10; // Hit bin two + 3: data <= 2'b11; // Hit bin three + 4: begin + $display("Coverage: %f%%", cg_inst.get_inst_coverage()); + if (cg_inst.get_inst_coverage() >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cg_inst.get_inst_coverage()); + $stop; + end + end + endcase + + // NOTE: NO manual .sample() call - relying on automatic sampling! + + // Auto-stop after 10 cycles to prevent infinite loop + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_auto_sample_timing.py b/test_regress/t/t_covergroup_auto_sample_timing.py new file mode 100755 index 000000000..071d14c14 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample_timing.py @@ -0,0 +1,22 @@ +#!/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 automatic sampling with --timing +test.scenarios('vlt') + +# Use the same .v file as the non-timing test +test.top_filename = "t/t_covergroup_auto_sample.v" + +test.compile(v_flags2=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_autobins.py b/test_regress/t/t_covergroup_autobins.py new file mode 100755 index 000000000..d2048aecb --- /dev/null +++ b/test_regress/t/t_covergroup_autobins.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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["-Wno-UNSIGNED -Wno-CMPCONST"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_autobins.v b/test_regress/t/t_covergroup_autobins.v new file mode 100644 index 000000000..d40277b2e --- /dev/null +++ b/test_regress/t/t_covergroup_autobins.v @@ -0,0 +1,122 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test automatic bin creation when coverpoint has no explicit bins + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [2:0] data3; // 3-bit: values 0-7 + logic [1:0] data2; // 2-bit: values 0-3 + + // Test 1: auto_bin_max default (64) - should create 8 bins for 3-bit signal + // Each value should get its own bin since 2^3 = 8 < 64 + covergroup cg1; + cp_data3: coverpoint data3; // No bins specified - autobins + endgroup + + // Test 2: With option.auto_bin_max = 4 + // Should create 4 bins: [0:1], [2:3], [4:5], [6:7] + covergroup cg2; + option.auto_bin_max = 4; + cp_data3: coverpoint data3; // No bins specified - autobins + endgroup + + // Test 3: With ignore bins - should still auto-create for non-ignored values + // Autobins created, but value 7 is ignored + covergroup cg3; + cp_data3: coverpoint data3 { + ignore_bins reserved = {7}; + } + endgroup + + // Test 4: Smaller signal - 2-bit + // Should create 4 bins (one per value) since 2^2 = 4 < 64 + covergroup cg4; + cp_data2: coverpoint data2; // No bins specified - autobins + endgroup + + // Test 5: With auto_bin_max smaller than signal range + // 2-bit signal (0-3) with auto_bin_max=2 should create 2 bins: [0:1], [2:3] + covergroup cg5; + option.auto_bin_max = 2; + cp_data2: coverpoint data2; // No bins specified - autobins + endgroup + + initial begin + cg1 cg1_inst; + cg2 cg2_inst; + cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + + cg1_inst = new; + cg2_inst = new; + cg3_inst = new; + cg4_inst = new; + cg5_inst = new; + + // Test CG1: Hit values 0, 1, 2 (3 of 8 bins = 37.5%) + data3 = 0; cg1_inst.sample(); + data3 = 1; cg1_inst.sample(); + data3 = 2; cg1_inst.sample(); + + // Test CG2: Hit values 0, 1, 4 (bins [0:1] and [4:5], 2 of 4 bins = 50%) + data3 = 0; cg2_inst.sample(); + data3 = 1; cg2_inst.sample(); + data3 = 4; cg2_inst.sample(); + + // Test CG3: Hit values 0, 1, 7 (7 is ignored, so 2 of 7 valid bins = 28.6%) + data3 = 0; cg3_inst.sample(); + data3 = 1; cg3_inst.sample(); + data3 = 7; cg3_inst.sample(); // Ignored + + // Test CG4: Hit all values 0-3 (4 of 4 bins = 100%) + data2 = 0; cg4_inst.sample(); + data2 = 1; cg4_inst.sample(); + data2 = 2; cg4_inst.sample(); + data2 = 3; cg4_inst.sample(); + + // Test CG5: Hit values 0, 3 (bins [0:1] and [2:3], 2 of 2 bins = 100%) + data2 = 0; cg5_inst.sample(); + data2 = 3; cg5_inst.sample(); + + $display("CG1 (8 autobins): %0.1f%%", cg1_inst.get_inst_coverage()); + $display("CG2 (4 autobins w/ option): %0.1f%%", cg2_inst.get_inst_coverage()); + $display("CG3 (7 autobins w/ ignore): %0.1f%%", cg3_inst.get_inst_coverage()); + $display("CG4 (4 autobins): %0.1f%%", cg4_inst.get_inst_coverage()); + $display("CG5 (2 autobins w/ option): %0.1f%%", cg5_inst.get_inst_coverage()); + + // Validate coverage results + if (cg1_inst.get_inst_coverage() < 30.0 || cg1_inst.get_inst_coverage() > 45.0) begin + $display("FAIL: CG1 coverage out of range"); + $stop; + end + if (cg2_inst.get_inst_coverage() < 45.0 || cg2_inst.get_inst_coverage() > 55.0) begin + $display("FAIL: CG2 coverage should be 50%% (2/4 bins with auto_bin_max=4)"); + $stop; + end + if (cg3_inst.get_inst_coverage() < 27.0 || cg3_inst.get_inst_coverage() > 30.0) begin + $display("FAIL: CG3 coverage should be ~28.6%% (2/7 valid bins, value 7 ignored)"); + $stop; + end + if (cg4_inst.get_inst_coverage() < 95.0) begin + $display("FAIL: CG4 coverage should be 100%%"); + $stop; + end + if (cg5_inst.get_inst_coverage() < 99.0) begin + $display("FAIL: CG5 coverage should be 100%% (2/2 bins with auto_bin_max=2)"); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out new file mode 100644 index 000000000..92812cc99 --- /dev/null +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_covergroup_autobins_bad.v:17:18: Automatic bins array size must be a constant + : ... note: In instance 't' + 17 | bins auto[size_var]; + | ^~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_autobins_bad.v:24:18: Automatic bins array size must be 1-10000, got 0 + : ... note: In instance 't' + 24 | bins auto[0]; + | ^~~~ +%Error: t/t_covergroup_autobins_bad.v:31:18: Automatic bins array size must be 1-10000, got 10001 + : ... note: In instance 't' + 31 | bins auto[10001]; + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.py b/test_regress/t/t_covergroup_autobins_bad.py new file mode 100755 index 000000000..7206b3e2f --- /dev/null +++ b/test_regress/t/t_covergroup_autobins_bad.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: 2025 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=['--error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v new file mode 100644 index 000000000..1d64fe8e0 --- /dev/null +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Tests for automatic bins error conditions + +module t; + int size_var; + logic [3:0] cp_expr; + + // Error: array size must be a constant + covergroup cg1; + cp1: coverpoint cp_expr { + bins auto[size_var]; + } + endgroup + + // Error: array size must be 1-10000 (zero) + covergroup cg2; + cp1: coverpoint cp_expr { + bins auto[0]; + } + endgroup + + // Error: array size must be 1-10000 (too large) + covergroup cg3; + cp1: coverpoint cp_expr { + bins auto[10001]; + } + endgroup + + cg1 cg1_inst = new; + cg2 cg2_inst = new; + cg3 cg3_inst = new; + + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_bin_counts.py b/test_regress/t/t_covergroup_bin_counts.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_bin_counts.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_bin_counts.v b/test_regress/t/t_covergroup_bin_counts.v new file mode 100644 index 000000000..48918b88f --- /dev/null +++ b/test_regress/t/t_covergroup_bin_counts.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test viewing individual bin hit counts + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [3:0] data; + + covergroup cg; + coverpoint data { + bins zero = {0}; + bins low = {[1:3]}; + bins mid = {[4:7]}; + bins high = {[8:15]}; + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + // Sample various values with different frequencies + data = 0; cg_inst.sample(); // zero: 1 + data = 1; cg_inst.sample(); // low: 1 + data = 2; cg_inst.sample(); // low: 2 + data = 2; cg_inst.sample(); // low: 3 + data = 5; cg_inst.sample(); // mid: 1 + data = 10; cg_inst.sample(); // high: 1 + + // Verify coverage is 100% (all 4 bins hit) + check_coverage(100.0, "final"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(real expected, string label); + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + if (cov < expected - 0.5 || cov > expected + 0.5) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_bin_options.v b/test_regress/t/t_covergroup_bin_options.v new file mode 100644 index 000000000..c2e1f1a8b --- /dev/null +++ b/test_regress/t/t_covergroup_bin_options.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test bin options: at_least, weight, goal + +module t; + /* verilator lint_off UNSIGNED */ + bit [7:0] addr; + + covergroup cg; + option.per_instance = 1; + option.comment = "Test covergroup with options"; + + coverpoint addr { + option.at_least = 2; // Each bin needs at least 2 hits + option.weight = 10; // This coverpoint has weight 10 + + bins low = {[0:3]}; + bins mid = {[4:7]}; + bins high = {[8:15]}; + } + endgroup + + initial begin + cg cg_inst; + real cov; + + cg_inst = new(); + + // Hit low once - should be 0% because at_least = 2 + addr = 2; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After 1 hit: %0.2f%%", cov); + if (cov != 0.0) begin + $error("Expected 0%% (bin needs 2 hits), got %0.2f%%", cov); + end + + // Hit low again - should be 33.33% (1/3 bins) + addr = 1; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After 2 hits to low: %0.2f%%", cov); + if (cov < 30.0 || cov > 35.0) begin + $error("Expected ~33.33%%, got %0.2f%%", cov); + end + + // Hit mid twice - should be 66.67% (2/3 bins) + addr = 5; cg_inst.sample(); + addr = 6; cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After mid hits: %0.2f%%", cov); + if (cov < 63.0 || cov > 70.0) begin + $error("Expected ~66.67%%, got %0.2f%%", cov); + end + + // Hit high twice - should be 100% + addr = 10; cg_inst.sample(); + addr = 12; cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After all bins hit: %0.2f%%", cov); + if (cov != 100.0) begin + $error("Expected 100%%, got %0.2f%%", cov); + end + + $display("Bin options test PASSED"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_bins_advanced.v b/test_regress/t/t_covergroup_bins_advanced.v new file mode 100644 index 000000000..d59c926ba --- /dev/null +++ b/test_regress/t/t_covergroup_bins_advanced.v @@ -0,0 +1,110 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test advanced bin types that ARE supported: +// - ignore_bins +// - wildcard bins +// - array bins (explicit values only, not ranges yet) + +module t; + + logic [3:0] data; + int error_count = 0; + + // Test 1: ignore_bins + covergroup cg_ignore; + coverpoint data { + bins low = {[0:3]}; + bins mid = {[4:7]}; + bins high = {[8:11]}; + ignore_bins reserved = {[12:15]}; // Should not count toward coverage + } + endgroup + + // Test 2: Array bins (with ranges - now working!) + covergroup cg_array; + coverpoint data { + bins values[] = {[0:3]}; // Creates 4 bins: values[0], values[1], values[2], values[3] + } + endgroup + + // Test 3: wildcard bins (with don't-care bits) + covergroup cg_wildcard; + coverpoint data { + wildcard bins pattern0 = {4'b00??}; // Matches 0,1,2,3 + wildcard bins pattern1 = {4'b01??}; // Matches 4,5,6,7 + wildcard bins pattern2 = {4'b10??}; // Matches 8,9,10,11 + wildcard bins pattern3 = {4'b11??}; // Matches 12,13,14,15 + } + endgroup + + initial begin + cg_ignore cg1; + cg_array cg2; + cg_wildcard cg3; + real cov; + + cg1 = new; + cg2 = new; + cg3 = new; + + // Test 1: ignore_bins + $display("Test 1: ignore_bins"); + data = 0; cg1.sample(); // low + data = 5; cg1.sample(); // mid + data = 9; cg1.sample(); // high + data = 12; cg1.sample(); // ignored - should not affect coverage + data = 13; cg1.sample(); // ignored + + cov = cg1.get_inst_coverage(); + $display(" Coverage with ignore_bins: %0.1f%% (expect 100%%)", cov); + // 3 out of 3 non-ignored bins = 100% + if (cov < 99.0 || cov > 101.0) begin + $display("%%Error: Expected 100%%, got %0.1f%%", cov); + error_count++; + end + + // Test 2: Array bins + $display("Test 2: Array bins (with ranges)"); + data = 0; cg2.sample(); // values[0] + data = 1; cg2.sample(); // values[1] + data = 2; cg2.sample(); // values[2] + // Note: values[3] not sampled, so 75% coverage expected + + cov = cg2.get_inst_coverage(); + $display(" Coverage with array bins: %0.1f%% (expect 75%%)", cov); + // 3 out of 4 bins = 75% + if (cov < 74.0 || cov > 76.0) begin + $display("%%Error: Expected 75%%, got %0.1f%%", cov); + error_count++; + end + + // Test 3: Wildcard bins + $display("Test 3: Wildcard bins"); + data = 2; cg3.sample(); // pattern0 (00??) + data = 5; cg3.sample(); // pattern1 (01??) + data = 10; cg3.sample(); // pattern2 (10??) + // pattern3 not sampled, so 75% coverage + + cov = cg3.get_inst_coverage(); + $display(" Coverage with wildcard bins: %0.1f%% (expect 75%%)", cov); + // 3 out of 4 bins = 75% + if (cov < 74.0 || cov > 76.0) begin + $display("%%Error: Expected 75%%, got %0.1f%%", cov); + error_count++; + end + + if (error_count == 0) begin + $write("*-* All Finished *-*\n"); + end else begin + $display("%%Error: %0d test(s) failed", error_count); + $stop; + end + + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_bins_default_illegal.v b/test_regress/t/t_covergroup_bins_default_illegal.v new file mode 100644 index 000000000..04e752188 --- /dev/null +++ b/test_regress/t/t_covergroup_bins_default_illegal.v @@ -0,0 +1,80 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test default bins and illegal_bins + +module t; + + logic [3:0] data; + int error_count = 0; + + // Test 1: default bins + covergroup cg_default; + coverpoint data { + bins special = {0, 5, 10}; + bins others = default; // Catch-all for uncovered values + } + endgroup + + // Test 2: illegal_bins (we'll test it doesn't crash on valid values) + covergroup cg_valid; + coverpoint data { + bins valid = {[0:10]}; + illegal_bins reserved = {[11:15]}; + } + endgroup + + initial begin + cg_default cg1; + cg_valid cg2; + real cov; + + cg1 = new; + cg2 = new; + + // Test 1: default bins + $display("Test 1: default bins"); + data = 0; cg1.sample(); // special bin + data = 1; cg1.sample(); // default/others bin + data = 5; cg1.sample(); // special bin + data = 7; cg1.sample(); // default/others bin + data = 10; cg1.sample(); // special bin + + cov = cg1.get_inst_coverage(); + $display(" Coverage with default bins: %0.1f%%", cov); + // Both bins hit: special (3 values: 0,5,10) and default (2 values: 1,7) + // Expected: 2/2 = 100% + if (cov < 99.0 || cov > 101.0) begin + $display("%%Error: Expected 100%%, got %0.1f%%", cov); + error_count++; + end + + // Test 2: illegal_bins (test with valid values only) + $display("Test 2: illegal_bins (sampling valid values)"); + data = 0; cg2.sample(); // valid + data = 5; cg2.sample(); // valid + data = 10; cg2.sample(); // valid + + cov = cg2.get_inst_coverage(); + $display(" Coverage with illegal_bins: %0.1f%%", cov); + // Only the valid bin counts, illegal bins don't count toward coverage + // 1 bin out of 1 = 100% + if (cov < 99.0 || cov > 101.0) begin + $display("%%Error: Expected 100%%, got %0.1f%%", cov); + error_count++; + end + + if (error_count == 0) begin + $write("*-* All Finished *-*\n"); + end else begin + $display("%%Error: %0d test(s) failed", error_count); + $stop; + end + + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_clocking_internal.py b/test_regress/t/t_covergroup_clocking_internal.py new file mode 100755 index 000000000..108fb561a --- /dev/null +++ b/test_regress/t/t_covergroup_clocking_internal.py @@ -0,0 +1,27 @@ +#!/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') + +# This test documents a known Verilator timing limitation: +# Internal clocks (generated via `always #5 clk = ~clk`) don't properly +# trigger procedural blocks in --timing mode. Even explicit .sample() calls +# in always @(posedge clk) blocks don't execute. +# +# Root cause: Timing scheduler doesn't trigger NBA/active regions for +# internally generated clock edges. +# +# Workaround: Use module input clocks (see t_covergroup_auto_sample.v) +test.compile(verilator_flags2=["--timing"]) + +test.execute(fails=True, expect=r'%Error: .*Timeout') + +test.passes() diff --git a/test_regress/t/t_covergroup_clocking_internal.v b/test_regress/t/t_covergroup_clocking_internal.v new file mode 100644 index 000000000..94abaec95 --- /dev/null +++ b/test_regress/t/t_covergroup_clocking_internal.v @@ -0,0 +1,76 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +// Test: Covergroup with INTERNAL clock using explicit sampling +// This demonstrates the workaround for internally generated clocks. +// +// Note: Auto-sampling with clocking events (@(posedge clk)) does NOT work +// for internal clocks due to Verilator timing scheduler limitations. +// The sample() call is generated but the NBA region isn't triggered. +// +// Solution: Call .sample() explicitly in an always block. + +module t; + logic clk = 0; + always #5 clk = ~clk; + + logic [1:0] data; + + /* verilator lint_off UNSIGNED */ + covergroup cg; // NOTE: No clocking event - we'll sample explicitly + cp: coverpoint data { + bins val0 = {2'b00}; + bins val1 = {2'b01}; + bins val2 = {2'b10}; + bins val3 = {2'b11}; + } + endgroup + /* verilator lint_on UNSIGNED */ + + cg cg_inst = new; + + // Explicit sampling workaround for internal clocks + always @(posedge clk) begin + cg_inst.sample(); + end + + initial begin + // Cycle 0 + data = 2'b00; + @(posedge clk); + + // Cycle 1 + data = 2'b01; + @(posedge clk); + + // Cycle 2 + data = 2'b10; + @(posedge clk); + + // Cycle 3 + data = 2'b11; + @(posedge clk); + + // Check coverage + #1; // Small delay to ensure last sample completes + + begin + automatic real cov = cg_inst.get_inst_coverage(); + $display("Coverage: %0.1f%%", cov); + + // Should have hit all 4 bins = 100% + if (cov >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cov); + $display("ERROR: This is a known limitation - auto-sampling doesn't work with internal clocks"); + $stop; + end + end + end + +endmodule diff --git a/test_regress/t/t_covergroup_clocking_module_input.py b/test_regress/t/t_covergroup_clocking_module_input.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_clocking_module_input.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_clocking_module_input.v b/test_regress/t/t_covergroup_clocking_module_input.v new file mode 100644 index 000000000..10d306bfd --- /dev/null +++ b/test_regress/t/t_covergroup_clocking_module_input.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +// Test: Covergroup with clocking event using MODULE INPUT clock +// Status: WORKS - Verilator correctly auto-samples when clk is a module port + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [1:0] data; + + /* verilator lint_off UNSIGNED */ + covergroup cg @(posedge clk); + cp: coverpoint data { + bins val0 = {2'b00}; + bins val1 = {2'b01}; + bins val2 = {2'b10}; + bins val3 = {2'b11}; + } + endgroup + /* verilator lint_on UNSIGNED */ + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + // Change data each cycle + data <= cyc[1:0]; + + if (cyc == 5) begin + /* verilator lint_off IMPLICITSTATIC */ + real cov = cg_inst.get_inst_coverage(); + /* verilator lint_on IMPLICITSTATIC */ + $display("Coverage: %0.1f%%", cov); + + // Should have hit all 4 bins (cycles 0-3) = 100% + if (cov >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cov); + $stop; + end + end + + if (cyc > 10) begin + $display("ERROR: Test timeout"); + $stop; + end + end + +endmodule diff --git a/test_regress/t/t_covergroup_coverage_pct.py b/test_regress/t/t_covergroup_coverage_pct.py new file mode 100755 index 000000000..17cca81f0 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_pct.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile(verilator_flags2=['--timing']) +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_coverage_pct.v b/test_regress/t/t_covergroup_coverage_pct.v new file mode 100644 index 000000000..291beadb6 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_pct.v @@ -0,0 +1,82 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [1:0] data; + + // Covergroup with 4 bins + covergroup cg @(posedge clk); + cp: coverpoint data { + bins low = {2'b00}; + bins mid1 = {2'b01}; + bins mid2 = {2'b10}; + bins high = {2'b11}; + } + endgroup + + cg cg_inst = new; + + initial begin + // Initially no bins covered - should be 0% + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage after 0 samples: %f", cov); + if (cov != 0.0) $stop; + + // Cover 1 bin (low) - should be 25% + @(posedge clk); + data = 2'b00; + @(posedge clk); + cov = cg_inst.get_inst_coverage(); + $display("Coverage after 1/4 bins: %f", cov); + if (cov < 24.9 || cov > 25.1) begin + $display("%%Error: Expected 25%%, got %f", cov); + $stop; + end + + // Cover 2nd bin (mid1) - should be 50% + @(posedge clk); + data = 2'b01; + @(posedge clk); + cov = cg_inst.get_inst_coverage(); + $display("Coverage after 2/4 bins: %f", cov); + if (cov < 49.9 || cov > 50.1) begin + $display("%%Error: Expected 50%%, got %f", cov); + $stop; + end + + // Cover 3rd bin (mid2) - should be 75% + @(posedge clk); + data = 2'b10; + @(posedge clk); + cov = cg_inst.get_inst_coverage(); + $display("Coverage after 3/4 bins: %f", cov); + if (cov < 74.9 || cov > 75.1) begin + $display("%%Error: Expected 75%%, got %f", cov); + $stop; + end + + // Cover 4th bin (high) - should be 100% + @(posedge clk); + data = 2'b11; + @(posedge clk); + cov = cg_inst.get_inst_coverage(); + $display("Coverage after 4/4 bins: %f", cov); + if (cov < 99.9 || cov > 100.1) begin + $display("%%Error: Expected 100%%, got %f", cov); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_coverage_query.py b/test_regress/t/t_covergroup_coverage_query.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_query.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_coverage_query.v b/test_regress/t/t_covergroup_coverage_query.v new file mode 100644 index 000000000..69f955df4 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_query.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test querying coverage values via get_inst_coverage + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [3:0] data; + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins mid = {[4:7]}; + bins high = {[8:15]}; + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + // Initially no coverage + check_coverage(0.0, "initial"); + + // Sample low bin - should be 33.33% (1 of 3 bins) + data = 1; + cg_inst.sample(); + check_coverage(33.33, "after low"); + + // Sample mid bin - should be 66.67% (2 of 3 bins) + data = 5; + cg_inst.sample(); + check_coverage(66.67, "after mid"); + + // Sample high bin - should be 100% (3 of 3 bins) + data = 10; + cg_inst.sample(); + check_coverage(100.0, "after high"); + + // Sample again - coverage should still be 100% + data = 2; + cg_inst.sample(); + check_coverage(100.0, "after resample"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(real expected, string label); + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + // Allow 0.5% tolerance for floating point + if (cov < expected - 0.5 || cov > expected + 0.5) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_coverpoints_unsup.out b/test_regress/t/t_covergroup_coverpoints_unsup.out index 5e65dcbeb..0d641afe0 100644 --- a/test_regress/t/t_covergroup_coverpoints_unsup.out +++ b/test_regress/t/t_covergroup_coverpoints_unsup.out @@ -1,20 +1,3 @@ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:21:19: Ignoring unsupported: coverage clocking event - 21 | covergroup cg @(posedge clk); - | ^ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:22:9: Ignoring unsupported: coverpoint - 22 | coverpoint a; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:24:31: Ignoring unsupported: cover bin specification - 24 | bins the_bins [5] = { [0:20] }; - | ^ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:23:9: Ignoring unsupported: coverpoint - 23 | coverpoint b { - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:21:5: Ignoring unsupported: covergroup - 21 | covergroup cg @(posedge clk); - | ^~~~~~~~~~ %Error: t/t_covergroup_coverpoints_unsup.v:35:48: Member 'a' not found in covergroup 'cg' : ... note: In instance 't' 35 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); diff --git a/test_regress/t/t_covergroup_cross_3way.py b/test_regress/t/t_covergroup_cross_3way.py new file mode 100755 index 000000000..962ebd1ea --- /dev/null +++ b/test_regress/t/t_covergroup_cross_3way.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile() +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_3way.v b/test_regress/t/t_covergroup_cross_3way.v new file mode 100644 index 000000000..501aadd8e --- /dev/null +++ b/test_regress/t/t_covergroup_cross_3way.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test 3-way cross coverage + +module t; + logic [1:0] addr; + logic cmd; + logic mode; + + // Covergroup with 3-way cross coverage + covergroup cg; + cp_addr: coverpoint addr { + bins addr0 = {0}; + bins addr1 = {1}; + bins addr2 = {2}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + cp_mode: coverpoint mode { + bins normal = {0}; + bins debug = {1}; + } + // 3-way cross: addr x cmd x mode = 3 x 2 x 2 = 12 cross bins + addr_cmd_mode: cross cp_addr, cp_cmd, cp_mode; + endgroup + + cg cg_inst = new; + + initial begin + // Hit different 3-way cross bins + addr = 0; cmd = 0; mode = 0; cg_inst.sample(); // addr0 x read x normal + $display("Sample 1: addr=%0d, cmd=%0d, mode=%0d", addr, cmd, mode); + + addr = 1; cmd = 1; mode = 0; cg_inst.sample(); // addr1 x write x normal + $display("Sample 2: addr=%0d, cmd=%0d, mode=%0d", addr, cmd, mode); + + addr = 2; cmd = 0; mode = 1; cg_inst.sample(); // addr2 x read x debug + $display("Sample 3: addr=%0d, cmd=%0d, mode=%0d", addr, cmd, mode); + + addr = 0; cmd = 1; mode = 1; cg_inst.sample(); // addr0 x write x debug + $display("Sample 4: addr=%0d, cmd=%0d, mode=%0d", addr, cmd, mode); + + addr = 1; cmd = 0; mode = 1; cg_inst.sample(); // addr1 x read x debug + $display("Sample 5: addr=%0d, cmd=%0d, mode=%0d", addr, cmd, mode); + + // Check coverage + // Total bins: + // - 3 bins in cp_addr (addr0, addr1, addr2) + // - 2 bins in cp_cmd (read, write) + // - 2 bins in cp_mode (normal, debug) + // - 12 bins in 3-way cross (3 x 2 x 2) + // Total = 19 bins + // Hit: addr0, addr1, addr2 (3), read, write (2), normal, debug (2), 5 cross bins + // Total = 12 out of 19 = 63.2% + $display("Coverage: %0.1f%%", cg_inst.get_inst_coverage()); + + if (cg_inst.get_inst_coverage() < 62.0 || cg_inst.get_inst_coverage() > 64.0) begin + $display("%%Error: Expected coverage around 63%%, got %0.1f%%", + cg_inst.get_inst_coverage()); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_cross_4way.py b/test_regress/t/t_covergroup_cross_4way.py new file mode 100755 index 000000000..962ebd1ea --- /dev/null +++ b/test_regress/t/t_covergroup_cross_4way.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile() +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_4way.v b/test_regress/t/t_covergroup_cross_4way.v new file mode 100644 index 000000000..788829086 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_4way.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test 4-way cross coverage + +module t; + logic [1:0] addr; + logic cmd; + logic mode; + logic parity; + + // Covergroup with 4-way cross coverage + covergroup cg; + cp_addr: coverpoint addr { + bins addr0 = {0}; + bins addr1 = {1}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + cp_mode: coverpoint mode { + bins normal = {0}; + bins debug = {1}; + } + cp_parity: coverpoint parity { + bins even = {0}; + bins odd = {1}; + } + // 4-way cross: addr x cmd x mode x parity = 2 x 2 x 2 x 2 = 16 cross bins + addr_cmd_mode_parity: cross cp_addr, cp_cmd, cp_mode, cp_parity; + endgroup + + cg cg_inst = new; + + initial begin + // Hit different 4-way cross bins + addr = 0; cmd = 0; mode = 0; parity = 0; cg_inst.sample(); + $display("Sample 1: addr=%0d, cmd=%0d, mode=%0d, parity=%0d", addr, cmd, mode, parity); + + addr = 1; cmd = 1; mode = 0; parity = 1; cg_inst.sample(); + $display("Sample 2: addr=%0d, cmd=%0d, mode=%0d, parity=%0d", addr, cmd, mode, parity); + + addr = 0; cmd = 1; mode = 1; parity = 0; cg_inst.sample(); + $display("Sample 3: addr=%0d, cmd=%0d, mode=%0d, parity=%0d", addr, cmd, mode, parity); + + addr = 1; cmd = 0; mode = 1; parity = 1; cg_inst.sample(); + $display("Sample 4: addr=%0d, cmd=%0d, mode=%0d, parity=%0d", addr, cmd, mode, parity); + + // Check coverage + // Total bins: + // - 2 bins in cp_addr + // - 2 bins in cp_cmd + // - 2 bins in cp_mode + // - 2 bins in cp_parity + // - 16 bins in 4-way cross (2 x 2 x 2 x 2) + // Total = 24 bins + // Hit: 2+2+2+2+4 = 12 out of 24 = 50% + $display("Coverage: %0.1f%%", cg_inst.get_inst_coverage()); + + if (cg_inst.get_inst_coverage() < 49.0 || cg_inst.get_inst_coverage() > 51.0) begin + $display("%%Error: Expected coverage around 50%%, got %0.1f%%", + cg_inst.get_inst_coverage()); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_cross_basic.v b/test_regress/t/t_covergroup_cross_basic.v new file mode 100644 index 000000000..1f5969099 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_basic.v @@ -0,0 +1,83 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test basic cross coverage functionality + +module t; + /* verilator lint_off UNSIGNED */ + bit [7:0] addr; + bit [7:0] cmd; + + covergroup cg; + // Two coverpoints with 2 bins each + a: coverpoint addr { + bins low = {[0:3]}; + bins high = {[4:7]}; + } + + b: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + + // 2-way cross creates 4 bins: lowread, lowwrite, highread, highwrite + c: cross a, b; + endgroup + + initial begin + cg cg_inst; + real cov; + + cg_inst = new(); + + // Initially coverage should be 0% + cov = cg_inst.get_inst_coverage(); + if (cov != 0.0) begin + $error("Initial coverage should be 0%%, got %0.2f%%", cov); + end + + // Hit lowread + addr = 2; cmd = 0; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + // Should have: a.low(1), b.read(1), c.low_x__read(1) = 3/8 = 37.5% + if (cov < 35.0 || cov > 40.0) begin + $error("After 1 sample, expected ~37.5%%, got %0.2f%%", cov); + end + + // Hit highwrite + addr = 5; cmd = 1; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + // Should have: a.low(1), a.high(1), b.read(1), b.write(1), + // c.low_x__read(1), c.high_x__write(1) = 6/8 = 75% + if (cov < 70.0 || cov > 80.0) begin + $error("After 2 samples, expected ~75%%, got %0.2f%%", cov); + end + + // Hit lowwrite + addr = 1; cmd = 1; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + // Should have 7/8 = 87.5% + if (cov < 85.0 || cov > 90.0) begin + $error("After 3 samples, expected ~87.5%%, got %0.2f%%", cov); + end + + // Hit highread for 100% coverage + addr = 7; cmd = 0; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + // Should have 8/8 = 100% + if (cov != 100.0) begin + $error("After all bins hit, expected 100%%, got %0.2f%%", cov); + end + + $display("Cross coverage test PASSED - final coverage: %0.2f%%", cov); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_cross_large.py b/test_regress/t/t_covergroup_cross_large.py new file mode 100755 index 000000000..e4626f4d9 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_large.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile(make_main=False, + verilator_flags2=["--coverage-user", "--exe", "t/t_covergroup_cross_large_main.cpp"]) +test.execute(check_finished=True) + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_large.v b/test_regress/t/t_covergroup_cross_large.v new file mode 100644 index 000000000..77b8c0155 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_large.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test large cross coverage with sparse map implementation + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int cyc = 0; + + logic [3:0] a; + logic [3:0] b; + logic [3:0] c; + logic [3:0] d; + + covergroup cg @(posedge clk); + option.per_instance = 1; + + // Each coverpoint has 4 bins, total cross: 4444 = 256 bins + // This exceeds threshold of 64, so should use sparse map + cp_a: coverpoint a { + bins a0 = {0,1,2,3}; + bins a1 = {4,5,6,7}; + bins a2 = {8,9,10,11}; + bins a3 = {12,13,14,15}; + } + + cp_b: coverpoint b { + bins b0 = {0,1,2,3}; + bins b1 = {4,5,6,7}; + bins b2 = {8,9,10,11}; + bins b3 = {12,13,14,15}; + } + + cp_c: coverpoint c { + bins c0 = {0,1,2,3}; + bins c1 = {4,5,6,7}; + bins c2 = {8,9,10,11}; + bins c3 = {12,13,14,15}; + } + + cp_d: coverpoint d { + bins d0 = {0,1,2,3}; + bins d1 = {4,5,6,7}; + bins d2 = {8,9,10,11}; + bins d3 = {12,13,14,15}; + } + + // 4-way cross: 4444 = 256 bins (> 64 threshold) + cross_abcd: cross cp_a, cp_b, cp_c, cp_d; + endgroup + + cg cg_inst = new; + + always @(posedge clk) begin + cyc <= cyc + 1; + + // Generate some cross coverage + a <= cyc[3:0]; + b <= cyc[7:4]; + c <= cyc[3:0]; // Intentionally correlate some + d <= cyc[7:4]; + + if (cyc == 20) begin + /* verilator lint_off IMPLICITSTATIC */ + real inst_cov = cg_inst.get_inst_coverage(); + /* verilator lint_on IMPLICITSTATIC */ + $display("Coverage: %0.1f%%", inst_cov); + + if (inst_cov < 1.0 || inst_cov > 100.0) begin + $display("%%Error: Invalid coverage value"); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_covergroup_cross_large_main.cpp b/test_regress/t/t_covergroup_cross_large_main.cpp new file mode 100644 index 000000000..b0d79ea49 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_large_main.cpp @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include "Vt_covergroup_cross_large.h" + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + const std::unique_ptr topp{ + new Vt_covergroup_cross_large{contextp.get()}}; + + topp->clk = 0; + + while (!contextp->gotFinish() && contextp->time() < 100) { + topp->clk = !topp->clk; + topp->eval(); + contextp->timeInc(1); + } + + topp->final(); + contextp->coveragep()->write(); + + return 0; +} diff --git a/test_regress/t/t_covergroup_cross_simple.py b/test_regress/t/t_covergroup_cross_simple.py new file mode 100755 index 000000000..962ebd1ea --- /dev/null +++ b/test_regress/t/t_covergroup_cross_simple.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile() +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_simple.v b/test_regress/t/t_covergroup_cross_simple.v new file mode 100644 index 000000000..085a721f8 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_simple.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test basic cross coverage with 2-way cross + +module t; + logic [1:0] addr; + logic cmd; + logic clk; + + // Covergroup with cross coverage + covergroup cg; + cp_addr: coverpoint addr { + bins addr0 = {0}; + bins addr1 = {1}; + bins addr2 = {2}; + bins addr3 = {3}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + // Cross coverage: addr x cmd = 4 x 2 = 8 bins + addr_cmd: cross cp_addr, cp_cmd; + endgroup + + cg cg_inst = new; + + initial begin + // Hit different cross bins + addr = 0; cmd = 0; cg_inst.sample(); // addr0 x read + $display("After sample 1: addr=%0d, cmd=%0d", addr, cmd); + + addr = 1; cmd = 1; cg_inst.sample(); // addr1 x write + $display("After sample 2: addr=%0d, cmd=%0d", addr, cmd); + + addr = 2; cmd = 0; cg_inst.sample(); // addr2 x read + $display("After sample 3: addr=%0d, cmd=%0d", addr, cmd); + + addr = 0; cmd = 1; cg_inst.sample(); // addr0 x write + $display("After sample 4: addr=%0d, cmd=%0d", addr, cmd); + + // Check coverage - should be 50% (4 out of 8 bins hit) + // Actually, with cross bins, we have: + // - 4 bins in cp_addr: addr0, addr1, addr2, addr3 + // - 2 bins in cp_cmd: read, write + // - 8 bins in cross (4 x 2) + // Hit: addr0, addr1, addr2 (3 bins), read, write (2 bins), 4 cross bins + // Total = 9 out of 14 = 64.3% + $display("Coverage: %0.1f%%", cg_inst.get_inst_coverage()); + + if (cg_inst.get_inst_coverage() < 63.0 || cg_inst.get_inst_coverage() > 65.0) begin + $display("%%Error: Expected coverage around 64%%, got %0.1f%%", + cg_inst.get_inst_coverage()); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_cross_small.v b/test_regress/t/t_covergroup_cross_small.v new file mode 100644 index 000000000..916e6f21d --- /dev/null +++ b/test_regress/t/t_covergroup_cross_small.v @@ -0,0 +1,60 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test small cross coverage with inline implementation + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int cyc = 0; + + logic [3:0] a; + logic [3:0] b; + + covergroup cg @(posedge clk); + option.per_instance = 1; + + // 2-way cross: 44 = 16 bins (< 64 threshold, should use inline) + cp_a: coverpoint a { + bins a0 = {0,1,2,3}; + bins a1 = {4,5,6,7}; + bins a2 = {8,9,10,11}; + bins a3 = {12,13,14,15}; + } + + cp_b: coverpoint b { + bins b0 = {0,1,2,3}; + bins b1 = {4,5,6,7}; + bins b2 = {8,9,10,11}; + bins b3 = {12,13,14,15}; + } + + cross_ab: cross cp_a, cp_b; + endgroup + + cg cg_inst = new; + + always @(posedge clk) begin + cyc <= cyc + 1; + + a <= cyc[3:0]; + b <= cyc[7:4]; + + if (cyc == 20) begin + /* verilator lint_off IMPLICITSTATIC */ + real inst_cov = cg_inst.get_inst_coverage(); + /* verilator lint_on IMPLICITSTATIC */ + $display("Coverage: %0.1f%%", inst_cov); + + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_covergroup_database.py b/test_regress/t/t_covergroup_database.py new file mode 100755 index 000000000..b1a3cf689 --- /dev/null +++ b/test_regress/t/t_covergroup_database.py @@ -0,0 +1,30 @@ +#!/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=['--coverage']) + +test.execute() + +# Check that coverage database contains functional coverage entries +# Format uses control characters as delimiters: C '^At^Bcovergroup^Apage...bin^Blow...h^Bcg.cp.low' count +test.file_grep(test.coverage_filename, r'covergroup') +test.file_grep(test.coverage_filename, r'bin.{0,2}low') # binlow with possible delimiter +test.file_grep(test.coverage_filename, r'bin.{0,2}high') # binhigh with possible delimiter +test.file_grep(test.coverage_filename, r'cg\.cp\.low') +test.file_grep(test.coverage_filename, r'cg\.cp\.high') + +# Verify both bins have non-zero counts (they were both sampled) +test.file_grep(test.coverage_filename, r'.*bin.{0,2}low.*\' [1-9]') +test.file_grep(test.coverage_filename, r'.*bin.{0,2}high.*\' [1-9]') + +test.passes() diff --git a/test_regress/t/t_covergroup_database.v b/test_regress/t/t_covergroup_database.v new file mode 100644 index 000000000..5b40792e5 --- /dev/null +++ b/test_regress/t/t_covergroup_database.v @@ -0,0 +1,38 @@ +// 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 + +// Test that functional coverage is properly written to coverage database +// Checks that coverage.dat contains covergroup entries with correct format + +// Expected coverage database entries will contain: +// - Type "covergroup" +// - Bin names ("low", "high") +// - Hierarchy ("cg.cp.low", "cg.cp.high") + +module t (/*AUTOARG*/); + logic [1:0] data; + + covergroup cg; + cp: coverpoint data { + bins low = {2'b00}; + bins high = {2'b11}; + } + endgroup + + cg cg_inst = new; + + initial begin + // Sample both bins + data = 2'b00; + cg_inst.sample(); + + data = 2'b11; + cg_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v new file mode 100644 index 000000000..ba5c370b2 --- /dev/null +++ b/test_regress/t/t_covergroup_default_bins.v @@ -0,0 +1,66 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test default bins - catch-all for values not in other bins + +module t; + /* verilator lint_off UNSIGNED */ + bit [7:0] data; + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins high = {[12:15]}; + bins other = default; // Catches everything else (4-11, 16+) + } + endgroup + + initial begin + cg cg_inst; + real cov; + + cg_inst = new(); + + // Hit low bin + data = 2; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After low: %0.2f%%", cov); + if (cov < 30.0 || cov > 35.0) begin + $error("Expected ~33.33%% (1/3 bins), got %0.2f%%", cov); + end + + // Hit high bin + data = 14; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After high: %0.2f%%", cov); + if (cov < 63.0 || cov > 70.0) begin + $error("Expected ~66.67%% (2/3 bins), got %0.2f%%", cov); + end + + // Hit default bin with value 7 (not in low or high) + data = 7; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After default (7): %0.2f%%", cov); + if (cov != 100.0) begin + $error("Expected 100%% (3/3 bins), got %0.2f%%", cov); + end + + // Hit another default value (should not increase coverage) + data = 20; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + if (cov != 100.0) begin + $error("Coverage should stay 100%%, got %0.2f%%", cov); + end + + $display("Default bins test PASSED"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_dynamic.v b/test_regress/t/t_covergroup_dynamic.v new file mode 100644 index 000000000..efad7d5c9 --- /dev/null +++ b/test_regress/t/t_covergroup_dynamic.v @@ -0,0 +1,93 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test dynamic covergroup creation with 'new' operator + +module t; + + covergroup cg; + coverpoint data { + bins low = {[0:1]}; + bins high = {[2:3]}; + } + endgroup + + int data; + + initial begin + cg cg_inst; + real cov; + + // Test 1: Create single dynamic instance + $display("Test 1: Single dynamic instance"); + cg_inst = new; + + // Initially no coverage + cov = cg_inst.get_inst_coverage(); + $display(" Initial coverage: %f", cov); + if (cov != 0.0) $stop; + + // Sample low bin + data = 0; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display(" After sampling low: %f", cov); + if (cov < 49.0 || cov > 51.0) $stop; // ~50% + + // Sample high bin + data = 2; + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display(" After sampling high: %f", cov); + if (cov < 99.0 || cov > 101.0) $stop; // ~100% + + // Test 2: Multiple dynamic instances + $display("Test 2: Multiple dynamic instances"); + begin + cg cg1, cg2, cg3; + + cg1 = new; + cg2 = new; + cg3 = new; + + // Sample different bins in each + data = 0; + cg1.sample(); + + data = 2; + cg2.sample(); + + data = 1; + cg3.sample(); + + // Check individual coverage + cov = cg1.get_inst_coverage(); + $display(" cg1 coverage: %f", cov); + if (cov < 49.0 || cov > 51.0) $stop; // 50% + + cov = cg2.get_inst_coverage(); + $display(" cg2 coverage: %f", cov); + if (cov < 49.0 || cov > 51.0) $stop; // 50% + + cov = cg3.get_inst_coverage(); + $display(" cg3 coverage: %f", cov); + if (cov < 49.0 || cov > 51.0) $stop; // 50% + end + + // Test 3: Reassignment (old instance should be cleaned up) + $display("Test 3: Instance reassignment"); + cg_inst = new; // Create new, old should be freed + + // New instance starts with 0% coverage + cov = cg_inst.get_inst_coverage(); + $display(" New instance coverage: %f", cov); + if (cov != 0.0) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_empty.cpp b/test_regress/t/t_covergroup_empty.cpp new file mode 100644 index 000000000..759c358d5 --- /dev/null +++ b/test_regress/t/t_covergroup_empty.cpp @@ -0,0 +1,28 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// Simple test harness for t_covergroup_empty - provides clock +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +#include "verilated.h" + +#include "Vt_covergroup_empty.h" + +int main(int argc, char** argv) { + Verilated::commandArgs(argc, argv); + Vt_covergroup_empty* top = new Vt_covergroup_empty; + + // Run for 20 cycles + for (int i = 0; i < 20; i++) { + top->clk = 0; + top->eval(); + top->clk = 1; + top->eval(); + + if (Verilated::gotFinish()) break; + } + + delete top; + return 0; +} diff --git a/test_regress/t/t_covergroup_empty.py b/test_regress/t/t_covergroup_empty.py new file mode 100755 index 000000000..1f645810b --- /dev/null +++ b/test_regress/t/t_covergroup_empty.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_empty.v b/test_regress/t/t_covergroup_empty.v new file mode 100644 index 000000000..c1b2ec4ef --- /dev/null +++ b/test_regress/t/t_covergroup_empty.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module - Edge case: empty covergroup +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +// Test: Empty covergroup (no coverpoints) +// Expected: Should compile, coverage should be 100% (nothing to cover) + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [7:0] value; + + // Empty covergroup - no coverpoints defined + covergroup cg_empty; + // Intentionally empty + endgroup + + cg_empty cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + value <= value + 1; + + cg_inst.sample(); + + if (cyc == 5) begin + // Get coverage - should be 100% (nothing to fail) + begin + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Empty covergroup coverage: %f%%", cov); + + // Empty covergroup should report 100% coverage + if (cov >= 99.9) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage for empty covergroup, got %f%%", cov); + $stop; + end + end + end + + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_extends.py b/test_regress/t/t_covergroup_extends.py index 10ad7f0de..25e90b5da 100755 --- a/test_regress/t/t_covergroup_extends.py +++ b/test_regress/t/t_covergroup_extends.py @@ -11,6 +11,11 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +# Covergroup inheritance with 'extends' is not yet supported +test.compile( + fails=test.vlt_all, + expect= + r'%Error: t/t_covergroup_extends.v:\d+:\d+: Unsupported: covergroup inheritance \(extends\) is not implemented' +) test.passes() diff --git a/test_regress/t/t_covergroup_extends_newfirst.py b/test_regress/t/t_covergroup_extends_newfirst.py index 10ad7f0de..71a498320 100755 --- a/test_regress/t/t_covergroup_extends_newfirst.py +++ b/test_regress/t/t_covergroup_extends_newfirst.py @@ -11,6 +11,11 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +# Covergroup inheritance with 'extends' is not yet supported +test.compile( + fails=test.vlt_all, + expect= + r'%Error: t/t_covergroup_extends_newfirst.v:\d+:\d+: Unsupported: covergroup inheritance \(extends\) is not implemented' +) test.passes() diff --git a/test_regress/t/t_covergroup_get_coverage.py b/test_regress/t/t_covergroup_get_coverage.py new file mode 100755 index 000000000..4348f3df1 --- /dev/null +++ b/test_regress/t/t_covergroup_get_coverage.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.compile() + +test.passes() diff --git a/test_regress/t/t_covergroup_get_coverage.v b/test_regress/t/t_covergroup_get_coverage.v new file mode 100644 index 000000000..be6fd2f08 --- /dev/null +++ b/test_regress/t/t_covergroup_get_coverage.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (input clk); + int value = 0; + + covergroup cg; + cp: coverpoint value { + bins low = {[0:5]}; + } + endgroup + + cg my_cg = new; + + always @(posedge clk) begin + real cov; + cov = my_cg.get_inst_coverage(); + my_cg.sample(); + end +endmodule diff --git a/test_regress/t/t_covergroup_iff.py b/test_regress/t/t_covergroup_iff.py new file mode 100755 index 000000000..4348f3df1 --- /dev/null +++ b/test_regress/t/t_covergroup_iff.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.compile() + +test.passes() diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v new file mode 100644 index 000000000..04d0ff0e8 --- /dev/null +++ b/test_regress/t/t_covergroup_iff.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (input clk); + logic enable = 0; + int value = 0; + + covergroup cg_iff; + cp_value: coverpoint value iff (enable) { + bins low = {[0:5]}; + bins mid = {[6:10]}; + } + endgroup + + cg_iff cg = new; + + always @(posedge clk) begin + cg.sample(); + end +endmodule diff --git a/test_regress/t/t_covergroup_ignore_bins.py b/test_regress/t/t_covergroup_ignore_bins.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_ignore_bins.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_ignore_bins.v b/test_regress/t/t_covergroup_ignore_bins.v new file mode 100644 index 000000000..6988ed1a4 --- /dev/null +++ b/test_regress/t/t_covergroup_ignore_bins.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test ignore_bins - excluded from coverage + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [3:0] data; + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins mid = {[4:7]}; + bins high = {[8:11]}; + ignore_bins reserved = {[12:15]}; // Should not count toward coverage + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + // Initially 0% (0 of 3 regular bins) + check_coverage(0.0, "initial"); + + // Hit reserved bin - should still be 0% + data = 13; + cg_inst.sample(); + check_coverage(0.0, "after reserved"); + + // Hit low bin - now 33.33% (1 of 3) + data = 1; + cg_inst.sample(); + check_coverage(33.33, "after low"); + + // Hit another reserved value - still 33.33% + data = 15; + cg_inst.sample(); + check_coverage(33.33, "after another reserved"); + + // Complete regular bins + data = 5; cg_inst.sample(); // mid + data = 10; cg_inst.sample(); // high + check_coverage(100.0, "complete"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(real expected, string label); + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + if (cov < expected - 0.5 || cov > expected + 0.5) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_illegal_bins.py b/test_regress/t/t_covergroup_illegal_bins.py new file mode 100755 index 000000000..d11b6a975 --- /dev/null +++ b/test_regress/t/t_covergroup_illegal_bins.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Test that illegal_bins are excluded from coverage (like ignore_bins) +# +# 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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_illegal_bins.v b/test_regress/t/t_covergroup_illegal_bins.v new file mode 100644 index 000000000..5a466881c --- /dev/null +++ b/test_regress/t/t_covergroup_illegal_bins.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + /* verilator lint_off UNSIGNED */ + logic [1:0] data; + + covergroup cg; + coverpoint data { + bins low = {0}; + bins mid = {1}; + bins high = {2}; + illegal_bins forbidden = {3}; + } + endgroup + + initial begin + automatic cg cg_inst = new; + + // Sample legal values only + data = 0; + cg_inst.sample(); + $display("Coverage after low: %f%% (expected ~33.33%%)", cg_inst.get_inst_coverage()); + + data = 1; + cg_inst.sample(); + $display("Coverage after mid: %f%% (expected ~66.67%%)", cg_inst.get_inst_coverage()); + + data = 2; + cg_inst.sample(); + $display("Coverage complete: %f%% (expected ~100.00%%)", cg_inst.get_inst_coverage()); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_in_class_duplicate_bad.out b/test_regress/t/t_covergroup_in_class_duplicate_bad.out index 448415232..29c600283 100644 --- a/test_regress/t/t_covergroup_in_class_duplicate_bad.out +++ b/test_regress/t/t_covergroup_in_class_duplicate_bad.out @@ -1,8 +1,8 @@ -%Error: t/t_covergroup_in_class_duplicate_bad.v:13:14: Duplicate declaration of CLASS '__vlAnonCG_embeddedCg': '__vlAnonCG_embeddedCg' +%Error: t/t_covergroup_in_class_duplicate_bad.v:13:3: Duplicate declaration of CLASS '__vlAnonCG_embeddedCg': '__vlAnonCG_embeddedCg' 13 | covergroup embeddedCg; - | ^~~~~~~~~~ - t/t_covergroup_in_class_duplicate_bad.v:9:14: ... Location of original declaration + | ^~~~~~~~~~ + t/t_covergroup_in_class_duplicate_bad.v:9:3: ... Location of original declaration 9 | covergroup embeddedCg; - | ^~~~~~~~~~ + | ^~~~~~~~~~ ... 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_covergroup_minimal.out b/test_regress/t/t_covergroup_minimal.out new file mode 100644 index 000000000..59007f7d3 --- /dev/null +++ b/test_regress/t/t_covergroup_minimal.out @@ -0,0 +1,2 @@ +Coverage: 0.0% +*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_minimal.py b/test_regress/t/t_covergroup_minimal.py new file mode 100755 index 000000000..897cb5ff1 --- /dev/null +++ b/test_regress/t/t_covergroup_minimal.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() + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_minimal.v b/test_regress/t/t_covergroup_minimal.v new file mode 100644 index 000000000..81899a32a --- /dev/null +++ b/test_regress/t/t_covergroup_minimal.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Minimal test for covergroup parsing and code generation + +module t; + int unsigned addr; // Use unsigned to avoid comparison warnings + + covergroup cg; + cp_addr: coverpoint addr { + bins low = {[0:127]}; + bins high = {[128:255]}; + } + endgroup + + initial begin + cg cg_inst; + cg_inst = new; + + // Sample some values + addr = 10; + cg_inst.sample(); + + addr = 200; + cg_inst.sample(); + + $display("Coverage: %0.1f%%", cg_inst.get_coverage()); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_mixed_bins.py b/test_regress/t/t_covergroup_mixed_bins.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_mixed_bins.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_mixed_bins.v b/test_regress/t/t_covergroup_mixed_bins.v new file mode 100644 index 000000000..69f0acf0d --- /dev/null +++ b/test_regress/t/t_covergroup_mixed_bins.v @@ -0,0 +1,59 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test mixed bin types: single values and ranges + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [7:0] opcode; + + covergroup cg; + coverpoint opcode { + bins nop = {8'h00}; + bins load = {8'h01, 8'h02, 8'h03}; + bins store = {8'h04, 8'h05}; + bins arith = {[8'h10:8'h1F]}; + bins others = {[8'h20:8'hFE]}; // Avoid 0xFF to prevent CMPCONST warning + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + // Test single value bins + opcode = 8'h00; cg_inst.sample(); // nop + check_coverage(20.0, "after nop"); + + // Test multi-value list bin + opcode = 8'h02; cg_inst.sample(); // load + check_coverage(40.0, "after load"); + + opcode = 8'h05; cg_inst.sample(); // store + check_coverage(60.0, "after store"); + + // Test range bin + opcode = 8'h15; cg_inst.sample(); // arith + check_coverage(80.0, "after arith"); + + opcode = 8'h80; cg_inst.sample(); // others + check_coverage(100.0, "after others"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(real expected, string label); + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + if (cov < expected - 0.5 || cov > expected + 0.5) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_multi_inst.py b/test_regress/t/t_covergroup_multi_inst.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_multi_inst.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_multi_inst.v b/test_regress/t/t_covergroup_multi_inst.v new file mode 100644 index 000000000..87c597f3d --- /dev/null +++ b/test_regress/t/t_covergroup_multi_inst.v @@ -0,0 +1,60 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test multiple covergroup instances with separate tracking + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [3:0] data1, data2; + + covergroup cg; + coverpoint data1 { + bins low = {[0:3]}; + bins high = {[4:15]}; + } + endgroup + + cg cg_inst1, cg_inst2; + + initial begin + cg_inst1 = new; + cg_inst2 = new; + + // Initially both have 0% coverage + check_coverage(cg_inst1, 0.0, "inst1 initial"); + check_coverage(cg_inst2, 0.0, "inst2 initial"); + + // Sample different values in each instance + data1 = 1; + cg_inst1.sample(); // inst1: low covered (50%) + check_coverage(cg_inst1, 50.0, "inst1 after low"); + check_coverage(cg_inst2, 0.0, "inst2 still empty"); + + data1 = 10; + cg_inst2.sample(); // inst2: high covered (50%) + check_coverage(cg_inst1, 50.0, "inst1 still 50%"); + check_coverage(cg_inst2, 50.0, "inst2 after high"); + + // Complete coverage in inst1 + data1 = 8; + cg_inst1.sample(); // inst1: both covered (100%) + check_coverage(cg_inst1, 100.0, "inst1 complete"); + check_coverage(cg_inst2, 50.0, "inst2 still 50%"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(cg inst, real expected, string label); + real cov; + cov = inst.get_inst_coverage(); + $display("Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + if (cov < expected - 0.5 || cov > expected + 0.5) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_multi_instance.py b/test_regress/t/t_covergroup_multi_instance.py new file mode 100755 index 000000000..1f645810b --- /dev/null +++ b/test_regress/t/t_covergroup_multi_instance.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_multi_instance.v b/test_regress/t/t_covergroup_multi_instance.v new file mode 100644 index 000000000..738b32f6d --- /dev/null +++ b/test_regress/t/t_covergroup_multi_instance.v @@ -0,0 +1,79 @@ +// DESCRIPTION: Verilator: Verilog Test module - Edge case: multiple instances +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +// Test: Multiple instances of same covergroup type sampling the same coverpoint +// Expected: Each instance tracks coverage independently, achieving same coverage +// since they all sample the same expression (value1) + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [2:0] value1; + + covergroup cg; + cp: coverpoint value1 { + bins low = {[0:3]}; + bins high = {[4:7]}; + } + endgroup + + // Create three independent instances + cg cg_inst1 = new; + cg cg_inst2 = new; + cg cg_inst3 = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: begin + value1 <= 1; // low bin for all instances + end + 1: begin + value1 <= 6; // high bin for all instances -> 100% + end + 2: begin + begin + real cov1, cov2, cov3; + cov1 = cg_inst1.get_inst_coverage(); + cov2 = cg_inst2.get_inst_coverage(); + cov3 = cg_inst3.get_inst_coverage(); + + $display("Instance 1 coverage: %f%%", cov1); + $display("Instance 2 coverage: %f%%", cov2); + $display("Instance 3 coverage: %f%%", cov3); + + // All instances sample the same coverpoint (value1), so they should all be 100% + // This tests that multiple instances track coverage independently, + // even when sampling the same expression + if (cov1 >= 99.0 && cov2 >= 99.0 && cov3 >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Coverage mismatch"); + $display(" Expected: inst1=100%%, inst2=100%%, inst3=100%%"); + $display(" Got: inst1=%f%%, inst2=%f%%, inst3=%f%%", cov1, cov2, cov3); + $stop; + end + end + end + endcase + + // Each instance samples the same value (value1) + // But tracks coverage independently + cg_inst1.sample(); + cg_inst2.sample(); + cg_inst3.sample(); + + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_negative_ranges.py b/test_regress/t/t_covergroup_negative_ranges.py new file mode 100755 index 000000000..1f645810b --- /dev/null +++ b/test_regress/t/t_covergroup_negative_ranges.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_negative_ranges.v b/test_regress/t/t_covergroup_negative_ranges.v new file mode 100644 index 000000000..ef94c09a0 --- /dev/null +++ b/test_regress/t/t_covergroup_negative_ranges.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module - Edge case: negative value ranges +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +// Test: Bins with negative value ranges +// Expected: Should handle negative numbers correctly + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int signed value; + + /* verilator lint_off CMPCONST */ + covergroup cg; + cp_neg: coverpoint value { + bins negative = {[-100:-1]}; + bins zero = {0}; + bins positive = {[1:100]}; + bins mixed = {[-10:10]}; + } + endgroup + /* verilator lint_on CMPCONST */ + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: value <= -50; // Hit negative bin + 1: value <= 0; // Hit zero bin + 2: value <= 50; // Hit positive bin + 3: value <= -5; // Hit mixed bin (also negative) + 4: value <= 5; // Hit mixed bin (also positive) + 5: begin + begin + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Coverage with negative ranges: %f%%", cov); + + // All 4 bins should be hit = 100% + if (cov >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cov); + $stop; + end + end + end + endcase + + cg_inst.sample(); + + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_option_unsup.out b/test_regress/t/t_covergroup_option_unsup.out new file mode 100644 index 000000000..075f08a3f --- /dev/null +++ b/test_regress/t/t_covergroup_option_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_covergroup_option_unsup.v:15:13: Ignoring unsupported coverage option: foobar + 15 | option.foobar = 1; + | ^~~~~~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_option_unsup.py b/test_regress/t/t_covergroup_option_unsup.py new file mode 100755 index 000000000..ef7407f24 --- /dev/null +++ b/test_regress/t/t_covergroup_option_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: 2025 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, fails=True) + +test.passes() diff --git a/test_regress/t/t_covergroup_option_unsup.v b/test_regress/t/t_covergroup_option_unsup.v new file mode 100644 index 000000000..ad78f4d1f --- /dev/null +++ b/test_regress/t/t_covergroup_option_unsup.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test: unsupported coverage option name in a coverpoint + +module t; + logic [3:0] cp_expr; + + covergroup cg; + cp1: coverpoint cp_expr { + option.foobar = 1; + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_perf.py b/test_regress/t/t_covergroup_perf.py new file mode 100755 index 000000000..f6fd4095d --- /dev/null +++ b/test_regress/t/t_covergroup_perf.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.compile(verilator_flags2=["--coverage-user"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_perf.v b/test_regress/t/t_covergroup_perf.v new file mode 100644 index 000000000..de7b32788 --- /dev/null +++ b/test_regress/t/t_covergroup_perf.v @@ -0,0 +1,113 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Performance test for functional coverage - measures sample() overhead + +module t; + logic [7:0] data; + logic [3:0] state; + logic [15:0] addr; + + // Large covergroup with multiple coverpoints and many bins + covergroup cg_perf; + // Coverpoint with many bins + cp_data: coverpoint data { + bins d0 = {0}; + bins d1 = {1}; + bins d2 = {2}; + bins d3 = {3}; + bins d4 = {4}; + bins d5 = {5}; + bins d6 = {6}; + bins d7 = {7}; + bins d8 = {8}; + bins d9 = {9}; + bins d10 = {[10:19]}; + bins d20 = {[20:29]}; + bins d30 = {[30:39]}; + bins d40 = {[40:49]}; + bins d50 = {[50:59]}; + bins rest = {[60:255]}; + } + + cp_state: coverpoint state { + bins s0 = {0}; + bins s1 = {1}; + bins s2 = {2}; + bins s3 = {3}; + bins s4 = {4}; + bins s5 = {5}; + bins s6 = {6}; + bins s7 = {7}; + bins s8 = {8}; + bins s9 = {9}; + bins s10 = {10}; + bins s11 = {11}; + bins s12 = {12}; + bins s13 = {13}; + bins s14 = {14}; + bins s15 = {15}; + } + + // verilator lint_off UNSIGNED + // verilator lint_off CMPCONST + cp_addr: coverpoint addr { + bins low = {[16'h0000:16'h03FF]}; // [0:1023] + bins mid = {[16'h0400:16'h07FF]}; // [1024:2047] + bins high = {[16'h0800:16'hFFFF]}; // [2048:65535] + } + // verilator lint_on CMPCONST + // verilator lint_on UNSIGNED + + // Cross coverage adds more bins + cross_data_state: cross cp_data, cp_state; + endgroup + + cg_perf cg_inst = new; + + initial begin + automatic longint start_time, end_time, elapsed; + automatic int iterations = 100000; + automatic real avg_time_ns; + + $display("=== Functional Coverage Performance Test ==="); + $display("Iterations: %0d", iterations); + + // Measure sample() overhead + start_time = $time; + + for (int i = 0; i < iterations; i++) begin + // Vary the data to hit different bins + data = i[7:0]; + state = i[3:0]; + addr = i[15:0]; + + cg_inst.sample(); + end + + end_time = $time; + elapsed = end_time - start_time; + + avg_time_ns = real'(elapsed) / real'(iterations); + + $display("Total time: %0d time units", elapsed); + $display("Average time per sample(): %0.2f time units", avg_time_ns); + $display("Coverage: %0.1f%%", cg_inst.get_inst_coverage()); + + // Performance target: < 100 cycles per sample() + // Assuming 1 time unit = 1 ns, typical CPU @ 3 GHz = 0.33 ns/cycle + // 100 cycles = 33 ns + if (avg_time_ns < 33.0) begin + $display("PASS: Performance within target (< 100 cycles)"); + end else begin + $display("WARNING: Performance may need optimization (> 100 cycles)"); + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_realistic.py b/test_regress/t/t_covergroup_realistic.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_realistic.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_realistic.v b/test_regress/t/t_covergroup_realistic.v new file mode 100644 index 000000000..43c717cb4 --- /dev/null +++ b/test_regress/t/t_covergroup_realistic.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Realistic example: Bus transaction coverage + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [31:0] addr; + logic [1:0] burst_type; + logic valid; + + // Coverage for a memory bus interface + covergroup bus_cg; + // Address coverage with interesting regions + coverpoint addr { + bins zero_page = {[32'h0000_0000:32'h0000_00FF]}; + bins boot_rom = {[32'h0000_1000:32'h0000_1FFF]}; + bins dram = {[32'h4000_0000:32'h7FFF_FFFF]}; + bins peripherals = {[32'h8000_0000:32'h9FFF_FFFF]}; + } + + // Burst type coverage (only when valid) + coverpoint burst_type iff (valid) { + bins single = {2'b00}; + bins incr = {2'b01}; + bins wrap = {2'b10}; + bins reserved = {2'b11}; + } + endgroup + + bus_cg cg_inst; + + initial begin + cg_inst = new; + + // Test various transactions + + // Boot sequence - should hit zero_page and boot_rom + valid = 1; + addr = 32'h0000_0010; burst_type = 2'b00; cg_inst.sample(); + addr = 32'h0000_1100; burst_type = 2'b01; cg_inst.sample(); + + // After boot + check_coverage(50.0, "after boot"); + + // DRAM access with wrap burst + addr = 32'h4000_0000; burst_type = 2'b10; cg_inst.sample(); + check_coverage(75.0, "after dram access"); + + // Peripheral access completes all addr bins + addr = 32'h8000_0100; burst_type = 2'b11; cg_inst.sample(); + check_coverage(100.0, "complete"); + + $write("*-* All Finished *-*\n"); + $finish; + end + + task check_coverage(real expected, string label); + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Bus Coverage %s: %0.2f%% (expected ~%0.2f%%)", label, cov, expected); + if (cov < expected - 1.0 || cov > expected + 1.0) begin + $error("Coverage mismatch: got %0.2f%%, expected ~%0.2f%%", cov, expected); + $stop; + end + endtask +endmodule diff --git a/test_regress/t/t_covergroup_sample_basic.py b/test_regress/t/t_covergroup_sample_basic.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_covergroup_sample_basic.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_sample_basic.v b/test_regress/t/t_covergroup_sample_basic.v new file mode 100644 index 000000000..f3e06a57c --- /dev/null +++ b/test_regress/t/t_covergroup_sample_basic.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test basic functional coverage sampling + +module t (/*AUTOARG*/); + /* verilator lint_off UNSIGNED */ + logic [3:0] data; + int cyc = 0; + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins mid = {[4:7]}; + bins high = {[8:15]}; + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + // Sample different values + data = 1; cg_inst.sample(); + data = 5; cg_inst.sample(); + data = 10; cg_inst.sample(); + data = 2; cg_inst.sample(); // low hit twice + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_simple.py b/test_regress/t/t_covergroup_simple.py new file mode 100755 index 000000000..e8cdbc78d --- /dev/null +++ b/test_regress/t/t_covergroup_simple.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_covergroup_simple.v b/test_regress/t/t_covergroup_simple.v new file mode 100644 index 000000000..79b01574c --- /dev/null +++ b/test_regress/t/t_covergroup_simple.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test basic covergroup with simple coverpoint + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [7:0] addr; + logic cmd; + + // Simple covergroup with two coverpoints + covergroup cg @(posedge clk); + cp_addr: coverpoint addr { + bins low = {[0:127]}; + bins high = {[128:255]}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + endgroup + + cg cg_inst = new; + + initial begin + // Sample some values + addr = 10; cmd = 0; + @(posedge clk); + + addr = 200; cmd = 1; + @(posedge clk); + + addr = 50; cmd = 0; + @(posedge clk); + + $display("Coverage: %0.1f%%", cg_inst.get_coverage()); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_static_coverage.py b/test_regress/t/t_covergroup_static_coverage.py new file mode 100755 index 000000000..be0e01535 --- /dev/null +++ b/test_regress/t/t_covergroup_static_coverage.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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Type-level (static) coverage using cg::get_coverage() compiles but returns placeholder value +# Test compiles successfully but runtime behavior is incorrect (returns 0.0) +test.compile() + +test.passes() diff --git a/test_regress/t/t_covergroup_static_coverage.v b/test_regress/t/t_covergroup_static_coverage.v new file mode 100644 index 000000000..2d068eba0 --- /dev/null +++ b/test_regress/t/t_covergroup_static_coverage.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// Test static get_coverage() with multiple instances + +module t; + + covergroup cg; + coverpoint data { + bins low = {[0:1]}; + bins mid = {[2:3]}; + bins high = {[4:5]}; + } + endgroup + + int data; + + initial begin + cg cg1, cg2, cg3; + real type_cov; + + cg1 = new; + cg2 = new; + cg3 = new; + + // Initially, no bins covered - should be 0% + type_cov = cg::get_coverage(); + $display("Initial type coverage: %f", type_cov); + if (type_cov != 0.0) $stop; + + // Sample cg1 with low bin + data = 0; + cg1.sample(); + type_cov = cg::get_coverage(); + $display("After cg1.sample(low): %f", type_cov); + // 1 bin covered out of 3 = 33.33% + if (type_cov < 33.0 || type_cov > 34.0) $stop; + + // Sample cg2 with mid bin + data = 2; + cg2.sample(); + type_cov = cg::get_coverage(); + $display("After cg2.sample(mid): %f", type_cov); + // 2 bins covered out of 3 = 66.67% + if (type_cov < 66.0 || type_cov > 67.0) $stop; + + // Sample cg3 with high bin + data = 4; + cg3.sample(); + type_cov = cg::get_coverage(); + $display("After cg3.sample(high): %f", type_cov); + // 3 bins covered out of 3 = 100% + if (type_cov < 99.9 || type_cov > 100.1) $stop; + + // Sample cg1 again with same bin - should not change coverage + data = 1; + cg1.sample(); + type_cov = cg::get_coverage(); + $display("After cg1.sample(low again): %f", type_cov); + if (type_cov < 99.9 || type_cov > 100.1) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans_3value.py b/test_regress/t/t_covergroup_trans_3value.py new file mode 100755 index 000000000..226a5a1f3 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_3value.py @@ -0,0 +1,19 @@ +#!/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') + +# Multi-value (3+) transition bins generate incomplete case statements +# This is a known limitation - complex transitions not fully supported +test.compile(fails=test.vlt_all, + expect=r'%Warning-CASEINCOMPLETE:.*Case values incompletely covered') + +test.passes() diff --git a/test_regress/t/t_covergroup_trans_3value.v b/test_regress/t/t_covergroup_trans_3value.v new file mode 100644 index 000000000..6004bf026 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_3value.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Test transition bins - 3-value sequences +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [2:0] state; + int errors = 0; + + covergroup cg; + cp_state: coverpoint state { + bins trans_3val = (0 => 1 => 2); // 3-value sequence + bins trans_3val_2 = (2 => 3 => 4); // Another 3-value sequence + } + endgroup + + cg cg_inst = new; + + initial begin + // Test sequence 1: 0 => 1 => 2 (should complete trans_3val) + state = 0; + cg_inst.sample(); + + state = 1; // 0 => 1 (state machine now at position 1) + cg_inst.sample(); + + state = 2; // 1 => 2 (completes trans_3val: 0=>1=>2) + cg_inst.sample(); + + // Test sequence 2: 2 => 3 => 4 (should complete trans_3val_2) + state = 3; // 2 => 3 (state machine now at position 1 for trans_3val_2) + cg_inst.sample(); + + state = 4; // 3 => 4 (completes trans_3val_2: 2=>3=>4) + cg_inst.sample(); + + // Check coverage + $display("Coverage: %f%%", cg_inst.get_inst_coverage()); + if (cg_inst.get_inst_coverage() < 99.0) begin + $display("ERROR: Expected 100%% coverage, got %f%%", cg_inst.get_inst_coverage()); + errors++; + end + + if (errors == 0) begin + $write("*-* All Finished *-*\n"); + end else begin + $display("*-* FAILED with %0d errors *-*", errors); + end + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans_empty_bad.out b/test_regress/t/t_covergroup_trans_empty_bad.out new file mode 100644 index 000000000..b645f182b --- /dev/null +++ b/test_regress/t/t_covergroup_trans_empty_bad.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_covergroup_trans_empty_bad.v:15:26: Ignoring unsupported: cover '[*' + 15 | bins t1 = (1 [*2]); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: t/t_covergroup_trans_empty_bad.v:15:18: Transition set without items + : ... note: In instance 't' + 15 | bins t1 = (1 [*2]); + | ^~ + ... 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_covergroup_trans_empty_bad.py b/test_regress/t/t_covergroup_trans_empty_bad.py new file mode 100755 index 000000000..ef7407f24 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_empty_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: 2025 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, fails=True) + +test.passes() diff --git a/test_regress/t/t_covergroup_trans_empty_bad.v b/test_regress/t/t_covergroup_trans_empty_bad.v new file mode 100644 index 000000000..f0fc3edc5 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_empty_bad.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test: transition bin with unsupported repetition operator causes empty transition set + +module t; + logic [3:0] cp_expr; + + covergroup cg; + cp1: coverpoint cp_expr { + bins t1 = (1 [*2]); + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_trans_ranges.py b/test_regress/t/t_covergroup_trans_ranges.py new file mode 100755 index 000000000..01b5938a5 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_ranges.py @@ -0,0 +1,17 @@ +#!/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') + +# Transition array bins are now supported +test.compile(verilator_flags2=["-Wno-IMPLICITSTATIC"]) + +test.passes() diff --git a/test_regress/t/t_covergroup_trans_ranges.v b/test_regress/t/t_covergroup_trans_ranges.v new file mode 100644 index 000000000..18db05b6d --- /dev/null +++ b/test_regress/t/t_covergroup_trans_ranges.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Test transition bins - array bins +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [2:0] state; + + covergroup cg; + // Test array bins: creates separate bin for each transition + cp_array: coverpoint state { + bins trans_array[] = (0 => 1), (1 => 2), (2 => 3); + } + endgroup + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: state <= 0; + 1: state <= 1; // 0 => 1 (hits trans_array[0=>1]) + 2: state <= 2; // 1 => 2 (hits trans_array[1=>2]) + 3: state <= 3; // 2 => 3 (hits trans_array[2=>3]) + 4: begin + real cov = cg_inst.get_inst_coverage(); + $display("Coverage: %f%%", cov); + // We should have hit all 3 array bins = 100% + if (cov >= 99.0) begin + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cov); + $stop; + end + end + endcase + + cg_inst.sample(); + + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_trans_restart.py b/test_regress/t/t_covergroup_trans_restart.py new file mode 100755 index 000000000..10bbe4350 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_restart.py @@ -0,0 +1,19 @@ +#!/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') + +# Multi-value transition bins with restart semantics generate incomplete case statements +# This is a known limitation - complex transitions not fully supported +test.compile(fails=test.vlt_all, + expect=r'%Warning-CASEINCOMPLETE:.*Case values incompletely covered') + +test.passes() diff --git a/test_regress/t/t_covergroup_trans_restart.v b/test_regress/t/t_covergroup_trans_restart.v new file mode 100644 index 000000000..ebc03fc53 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_restart.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Test transition bins - restart behavior +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [2:0] state; + int errors = 0; + + covergroup cg; + cp_state: coverpoint state { + bins trans_restart = (1 => 2 => 3); // Should handle restart correctly + } + endgroup + + cg cg_inst = new; + + initial begin + // Sequence: 1, 2, 1, 2, 3 + // This tests restart logic: when we see 1 again while in middle of sequence, + // we should restart from position 1 (not reset to 0) + + state = 1; // Start: position = 1 + cg_inst.sample(); + $display("After state=1: seqpos should be 1"); + + state = 2; // Advance: position = 2 + cg_inst.sample(); + $display("After state=2: seqpos should be 2"); + + state = 1; // Restart! Should go to position 1 (not 0) + cg_inst.sample(); + $display("After state=1 (restart): seqpos should be 1"); + + state = 2; // Advance: position = 2 + cg_inst.sample(); + $display("After state=2: seqpos should be 2"); + + state = 3; // Complete! Bin should increment + cg_inst.sample(); + $display("After state=3: bin should have incremented, seqpos reset to 0"); + + // Check coverage + $display("Coverage: %f%%", cg_inst.get_inst_coverage()); + if (cg_inst.get_inst_coverage() < 99.0) begin + $display("ERROR: Expected 100%% coverage, got %f%%", cg_inst.get_inst_coverage()); + errors++; + end + + if (errors == 0) begin + $write("*-* All Finished *-*\n"); + end else begin + $display("*-* FAILED with %0d errors *-*", errors); + end + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans_simple.v b/test_regress/t/t_covergroup_trans_simple.v new file mode 100644 index 000000000..82c411989 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_simple.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Test transition bins - simple two-value transitions +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [2:0] state; + + covergroup cg; + cp_state: coverpoint state { + bins trans1 = (0 => 1); + bins trans2 = (1 => 2); + bins trans3 = (2 => 3); + } + endgroup + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: state <= 0; + 1: state <= 1; // 0 => 1 (trans1 should hit) + 2: state <= 2; // 1 => 2 (trans2 should hit) + 3: state <= 3; // 2 => 3 (trans3 should hit) + 4: begin + $display("Coverage: %f%%", cg_inst.get_inst_coverage()); + if (cg_inst.get_inst_coverage() >= 99.0) begin // Allow for rounding + $write("*-* All Finished *-*\n"); + $finish; + end else begin + $display("ERROR: Expected 100%% coverage, got %f%%", cg_inst.get_inst_coverage()); + $stop; + end + end + endcase + + // Sample the covergroup manually each clock + cg_inst.sample(); + + // Auto-stop after 10 cycles to prevent infinite loop + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_trans_single_bad.out b/test_regress/t/t_covergroup_trans_single_bad.out new file mode 100644 index 000000000..372288ccb --- /dev/null +++ b/test_regress/t/t_covergroup_trans_single_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_covergroup_trans_single_bad.v:15:18: Transition requires at least two values + : ... note: In instance 't' + 15 | bins t1 = (1); + | ^~ + ... 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_covergroup_trans_single_bad.py b/test_regress/t/t_covergroup_trans_single_bad.py new file mode 100755 index 000000000..ef7407f24 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_single_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: 2025 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, fails=True) + +test.passes() diff --git a/test_regress/t/t_covergroup_trans_single_bad.v b/test_regress/t/t_covergroup_trans_single_bad.v new file mode 100644 index 000000000..02d665b94 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_single_bad.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test: transition bin requires at least two values + +module t; + logic [3:0] cp_expr; + + covergroup cg; + cp1: coverpoint cp_expr { + bins t1 = (1); + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index 963e36b49..614c42239 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -1,188 +1,32 @@ -%Warning-COVERIGN: t/t_covergroup_unsup.v:39:4: Ignoring unsupported: covergroup - 39 | covergroup cg_empty; - | ^~~~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_covergroup_unsup.v:42:4: Ignoring unsupported: covergroup - 42 | covergroup cg_opt; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:61:34: Ignoring unsupported: coverage clocking event - 61 | covergroup cg_clockingevent() @(posedge clk); - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:61:4: Ignoring unsupported: covergroup - 61 | covergroup cg_clockingevent() @(posedge clk); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:63:4: Ignoring unsupported: covergroup - 63 | covergroup cg_withfunction() with function sample (a); - | ^~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:65:25: Ignoring unsupported: coverage '@@' events 65 | covergroup cg_atat() @@ (begin funca or end funcb); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:65:4: Ignoring unsupported: covergroup - 65 | covergroup cg_atat() @@ (begin funca or end funcb); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:67:4: Ignoring unsupported: covergroup - 67 | covergroup cg_bracket; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:70:4: Ignoring unsupported: covergroup - 70 | covergroup cg_bracket2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:74:7: Ignoring unsupported: coverpoint - 74 | coverpoint a; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:73:4: Ignoring unsupported: covergroup - 73 | covergroup cg_cp; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:77:20: Ignoring unsupported: cover 'iff' - 77 | coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:77:7: Ignoring unsupported: coverpoint - 77 | coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:76:4: Ignoring unsupported: covergroup - 76 | covergroup cg_cp_iff; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:80:24: Ignoring unsupported: cover 'iff' - 80 | id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:80:11: Ignoring unsupported: coverpoint - 80 | id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:79:4: Ignoring unsupported: covergroup - 79 | covergroup cg_id_cp_iff; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:83:28: Ignoring unsupported: cover 'iff' - 83 | int id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:83:15: Ignoring unsupported: coverpoint - 83 | int id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:82:4: Ignoring unsupported: covergroup - 82 | covergroup cg_id_cp_id1; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:86:32: Ignoring unsupported: cover 'iff' - 86 | var int id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:86:19: Ignoring unsupported: coverpoint - 86 | var int id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:85:4: Ignoring unsupported: covergroup - 85 | covergroup cg_id_cp_id2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:89:34: Ignoring unsupported: cover 'iff' - 89 | var [3:0] id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:89:21: Ignoring unsupported: coverpoint - 89 | var [3:0] id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:88:4: Ignoring unsupported: covergroup - 88 | covergroup cg_id_cp_id3; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:92:30: Ignoring unsupported: cover 'iff' - 92 | [3:0] id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:92:17: Ignoring unsupported: coverpoint - 92 | [3:0] id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:91:4: Ignoring unsupported: covergroup - 91 | covergroup cg_id_cp_id4; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:95:31: Ignoring unsupported: cover 'iff' - 95 | signed id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:95:18: Ignoring unsupported: coverpoint - 95 | signed id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:94:4: Ignoring unsupported: covergroup - 94 | covergroup cg_id_cp_id5; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:99:18: Ignoring unsupported: cover 'iff' + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_covergroup_unsup.v:99:23: Ignoring unsupported: cross iff condition 99 | cross a, b iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:99:7: Ignoring unsupported: cover cross - 99 | cross a, b iff (!rst); - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:98:4: Ignoring unsupported: covergroup - 98 | covergroup cg_cross; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:102:18: Ignoring unsupported: cover 'iff' + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:102:23: Ignoring unsupported: cross iff condition 102 | cross a, b iff (!rst) {} - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:102:7: Ignoring unsupported: cover cross - 102 | cross a, b iff (!rst) {} - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:101:4: Ignoring unsupported: covergroup - 101 | covergroup cg_cross2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:105:7: Ignoring unsupported: cover cross - 105 | cross a, b { option.comment = "cross"; option.weight = 12; } - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:104:4: Ignoring unsupported: covergroup - 104 | covergroup cg_cross3; - | ^~~~~~~~~~ + | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:109:24: Ignoring unsupported: coverage cross 'function' declaration 109 | function void crossfunc; endfunction | ^~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:110:21: Ignoring unsupported: coverage select function call 110 | bins one = crossfunc(); | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:110:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:110:10: Ignoring unsupported: explicit coverage cross bins 110 | bins one = crossfunc(); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:108:7: Ignoring unsupported: cover cross - 108 | cross a, b { - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:107:4: Ignoring unsupported: covergroup - 107 | covergroup cg_cross4; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:114:28: Ignoring unsupported: cover 'iff' +%Warning-COVERIGN: t/t_covergroup_unsup.v:114:33: Ignoring unsupported: cross iff condition 114 | my_cg_id: cross a, b iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:114:17: Ignoring unsupported: cover cross - 114 | my_cg_id: cross a, b iff (!rst); - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:113:4: Ignoring unsupported: covergroup - 113 | covergroup cg_cross_id; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:119:17: Ignoring unsupported: cover bin specification - 119 | { bins ba = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:120:24: Ignoring unsupported: cover 'iff' - 120 | { bins bar = {a} iff (!rst); } - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:120:18: Ignoring unsupported: cover bin specification - 120 | { bins bar = {a} iff (!rst); } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:121:26: Ignoring unsupported: cover bin specification - 121 | { illegal_bins ila = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:122:25: Ignoring unsupported: cover bin specification - 122 | { ignore_bins iga = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:124:19: Ignoring unsupported: cover bin specification - 124 | { bins ba[] = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:125:20: Ignoring unsupported: cover bin specification - 125 | { bins ba[2] = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:127:23: Ignoring unsupported: cover bin 'with' specification - 127 | { bins ba = {a} with ( b ); } - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:129:27: Ignoring unsupported: cover bin 'wildcard' specification - 129 | { wildcard bins bwa = {a}; } - | ^ + | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:130:34: Ignoring unsupported: cover bin 'wildcard' 'with' specification 130 | { wildcard bins bwaw = {a} with ( b ); } | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:132:20: Ignoring unsupported: cover bin 'default' - 132 | { bins def = default; } - | ^~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:133:29: Ignoring unsupported: cover bin 'default' 'sequence' 133 | { bins defs = default sequence; } | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:135:18: Ignoring unsupported: cover bin trans list - 135 | { bins bts = ( 1, 2 ); } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:136:9: Ignoring unsupported: cover bin 'wildcard' trans list 136 | { wildcard bins wbts = ( 1, 2 ); } | ^~~~~~~~ @@ -195,121 +39,82 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:137:59: Ignoring unsupported: covergroup value range 137 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:137:19: Ignoring unsupported: cover bin trans list - 137 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:139:27: Ignoring unsupported: cover trans set '=>' - 139 | { bins bts2 = ( 1,5 => 6,7 ) ; } - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:139:19: Ignoring unsupported: cover bin trans list - 139 | { bins bts2 = ( 1,5 => 6,7 ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:140:25: Ignoring unsupported: cover '[*' 140 | { bins bts2 = ( 3 [*5] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:140:19: Ignoring unsupported: cover bin trans list - 140 | { bins bts2 = ( 3 [*5] ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:141:25: Ignoring unsupported: cover '[*' 141 | { bins bts2 = ( 3 [*5:6] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:141:19: Ignoring unsupported: cover bin trans list - 141 | { bins bts2 = ( 3 [*5:6] ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:142:25: Ignoring unsupported: cover '[->' 142 | { bins bts2 = ( 3 [->5] ) ; } | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:142:19: Ignoring unsupported: cover bin trans list - 142 | { bins bts2 = ( 3 [->5] ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:143:25: Ignoring unsupported: cover '[->' 143 | { bins bts2 = ( 3 [->5:6] ) ; } | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:143:19: Ignoring unsupported: cover bin trans list - 143 | { bins bts2 = ( 3 [->5:6] ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:144:25: Ignoring unsupported: cover '[=' 144 | { bins bts2 = ( 3 [=5] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:144:19: Ignoring unsupported: cover bin trans list - 144 | { bins bts2 = ( 3 [=5] ) ; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:145:25: Ignoring unsupported: cover '[=' 145 | { bins bts2 = ( 3 [=5:6] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:145:19: Ignoring unsupported: cover bin trans list - 145 | { bins bts2 = ( 3 [=5:6] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:117:4: Ignoring unsupported: covergroup - 117 | covergroup cg_binsoroptions_bk1; - | ^~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:150:26: Ignoring unsupported: cover bin 'with' specification 150 | bins div_by_2 = a with (item % 2 == 0); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:151:34: Ignoring unsupported: cover bin 'with' specification 151 | bins div_by_2_paren[] = a with (item % 2 == 0); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:149:6: Ignoring unsupported: coverpoint - 149 | coverpoint a { - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:148:4: Ignoring unsupported: covergroup - 148 | covergroup cg_coverpoint_ref; - | ^~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:157:23: Ignoring unsupported: coverage select expression 'binsof' 157 | bins bin_a = binsof(a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:157:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:157:10: Ignoring unsupported: explicit coverage cross bins 157 | bins bin_a = binsof(a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:158:24: Ignoring unsupported: coverage select expression 'binsof' 158 | bins bin_ai = binsof(a) iff (!rst); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:158:34: Ignoring unsupported: cover 'iff' - 158 | bins bin_ai = binsof(a) iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:158:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:158:10: Ignoring unsupported: explicit coverage cross bins 158 | bins bin_ai = binsof(a) iff (!rst); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:159:23: Ignoring unsupported: coverage select expression 'binsof' 159 | bins bin_c = binsof(cp.x); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:159:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:159:10: Ignoring unsupported: explicit coverage cross bins 159 | bins bin_c = binsof(cp.x); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:160:24: Ignoring unsupported: coverage select expression 'binsof' 160 | bins bin_na = ! binsof(a); | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:160:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:160:10: Ignoring unsupported: explicit coverage cross bins 160 | bins bin_na = ! binsof(a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:162:33: Ignoring unsupported: coverage select expression 'intersect' 162 | bins bin_d = binsof(a) intersect { b }; | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:162:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:162:10: Ignoring unsupported: explicit coverage cross bins 162 | bins bin_d = binsof(a) intersect { b }; | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:163:34: Ignoring unsupported: coverage select expression 'intersect' 163 | bins bin_nd = ! binsof(a) intersect { b }; | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:163:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:163:10: Ignoring unsupported: explicit coverage cross bins 163 | bins bin_nd = ! binsof(a) intersect { b }; | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:165:23: Ignoring unsupported: coverage select expression with 165 | bins bin_e = with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:165:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:165:10: Ignoring unsupported: explicit coverage cross bins 165 | bins bin_e = with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:166:27: Ignoring unsupported: coverage select expression with 166 | bins bin_not_e = ! with (a); | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:166:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:166:10: Ignoring unsupported: explicit coverage cross bins 166 | bins bin_not_e = ! with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:168:26: Ignoring unsupported: coverage select expression 'binsof' 168 | bins bin_par = (binsof(a)); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:168:10: Ignoring unsupported: explicit coverage cross bins 168 | bins bin_par = (binsof(a)); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:169:25: Ignoring unsupported: coverage select expression 'binsof' @@ -321,7 +126,7 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:169:35: Ignoring unsupported: coverage select expression '&&' 169 | bins bin_and = binsof(a) && binsof(b); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:169:10: Ignoring unsupported: explicit coverage cross bins 169 | bins bin_and = binsof(a) && binsof(b); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:170:24: Ignoring unsupported: coverage select expression 'binsof' @@ -333,7 +138,7 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:170:34: Ignoring unsupported: coverage select expression '||' 170 | bins bin_or = binsof(a) || binsof(b); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:170:10: Ignoring unsupported: explicit coverage cross bins 170 | bins bin_or = binsof(a) || binsof(b); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:171:26: Ignoring unsupported: coverage select expression 'binsof' @@ -342,7 +147,7 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:171:36: Ignoring unsupported: coverage select expression with 171 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:171:10: Ignoring unsupported: explicit coverage cross bins 171 | bins bin_with = binsof(a) with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:172:29: Ignoring unsupported: coverage select expression 'binsof' @@ -357,7 +162,7 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:172:39: Ignoring unsupported: coverage select expression '||' 172 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:172:10: Ignoring unsupported: explicit coverage cross bins 172 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:30: Ignoring unsupported: coverage select expression 'binsof' @@ -372,37 +177,44 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:40: Ignoring unsupported: coverage select expression '&&' 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:173:10: Ignoring unsupported: explicit coverage cross bins 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:174:37: Ignoring unsupported: coverage select expression 'binsof' 174 | bins bin_multiple_fields = binsof(p.inner_packet.field); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:174:10: Ignoring unsupported: coverage cross bin +%Warning-COVERIGN: t/t_covergroup_unsup.v:174:10: Ignoring unsupported: explicit coverage cross bins 174 | bins bin_multiple_fields = binsof(p.inner_packet.field); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:156:7: Ignoring unsupported: cover cross - 156 | cross a, b { - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:155:4: Ignoring unsupported: covergroup - 155 | covergroup cg_cross_bins; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:178:4: Ignoring unsupported: covergroup - 178 | covergroup cgArgs(int cg_lim); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:185:23: Ignoring unsupported: coverage clocking event - 185 | covergroup cov1 @m_z; - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:186:10: Ignoring unsupported: coverpoint - 186 | coverpoint m_x; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:187:10: Ignoring unsupported: coverpoint - 187 | coverpoint m_y; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:185:7: Ignoring unsupported: covergroup - 185 | covergroup cov1 @m_z; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:195:7: Ignoring unsupported: covergroup +%Warning-COVERIGN: t/t_covergroup_unsup.v:195:7: Ignoring unsupported: covergroup inheritance (extends) 195 | covergroup extends cg_empty; | ^~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:99:13: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 99 | cross a, b iff (!rst); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:102:13: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 102 | cross a, b iff (!rst) {} + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:105:13: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 105 | cross a, b { option.comment = "cross"; option.weight = 12; } + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:108:13: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 108 | cross a, b { + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:114:23: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 114 | my_cg_id: cross a, b iff (!rst); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:156:13: Ignoring unsupported: cross references unknown coverpoint: a + : ... note: In instance 't' + 156 | cross a, b { + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:185:7: Ignoring unsupported: covergroup clocking event on member variable + : ... note: In instance 't' + 185 | covergroup cov1 @m_z; + | ^~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_wildcard_bins.v b/test_regress/t/t_covergroup_wildcard_bins.v new file mode 100644 index 000000000..542294864 --- /dev/null +++ b/test_regress/t/t_covergroup_wildcard_bins.v @@ -0,0 +1,79 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Matthew Ballance +// SPDX-License-Identifier: CC0-1.0 + +// Test wildcard bins with don't care matching + +module t; + /* verilator lint_off UNSIGNED */ + bit [7:0] data; + + covergroup cg; + coverpoint data { + // Match any value with upper nibble = 4'b0000 + wildcard bins low = {8'b0000_????}; + + // Match any value with upper nibble = 4'b1111 + wildcard bins high = {8'b1111_????}; + + // Match specific pattern with don't cares + wildcard bins pattern = {8'b10?0_11??}; + } + endgroup + + initial begin + cg cg_inst; + real cov; + + cg_inst = new(); + + // Test low bin (upper nibble = 0000) + data = 8'b0000_0101; // Should match 'low' + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After sample 1 (low): %0.2f%%", cov); + if (cov < 30.0 || cov > 35.0) begin + $error("Expected ~33.33%% (1/3 bins), got %0.2f%%", cov); + end + + // Test high bin (upper nibble = 1111) + data = 8'b1111_1010; // Should match 'high' + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After sample 2 (high): %0.2f%%", cov); + if (cov < 63.0 || cov > 70.0) begin + $error("Expected ~66.67%% (2/3 bins), got %0.2f%%", cov); + end + + // Test pattern bin (10?0_11??) + data = 8'b1000_1101; // Should match 'pattern' (10[0]0_11[0]1) + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + $display("After sample 3 (pattern): %0.2f%%", cov); + if (cov != 100.0) begin + $error("Expected 100%% (3/3 bins), got %0.2f%%", cov); + end + + // Verify another pattern match + data = 8'b1010_1111; // Should also match 'pattern' (10[1]0_11[1]1) + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + if (cov != 100.0) begin + $error("Pattern should still be 100%%, got %0.2f%%", cov); + end + + // Verify non-matching value doesn't change coverage + data = 8'b0101_0101; // Shouldn't match any bin + cg_inst.sample(); + cov = cg_inst.get_inst_coverage(); + if (cov != 100.0) begin + $error("Non-matching value shouldn't change coverage, got %0.2f%%", cov); + end + + $display("Wildcard bins test PASSED - final coverage: %0.2f%%", cov); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index aecca1cf7..ea9eaa8c6 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -33,7 +33,9 @@ module Vt_debug_emitv_t; function ident; input int signed value; begin : label0 - ident = /*CRESET*/; + ident = + ???? // CRESET + ; ident = value; disable label0; end diff --git a/test_regress/t/t_dist_warn_coverage.py b/test_regress/t/t_dist_warn_coverage.py index cc209bdbc..7d4ee1210 100755 --- a/test_regress/t/t_dist_warn_coverage.py +++ b/test_regress/t/t_dist_warn_coverage.py @@ -167,6 +167,10 @@ for s in [ 'is not an unpacked array, but is in an unpacked array context', 'loading other than unpacked-array variable', 'loading other than unpacked/associative-array variable', + # These are safety limits requiring >1000 bins or >10000 members to trigger + 'Too many bins or infinite loop detected in bin iteration', + 'Too many members or infinite loop in membersp iteration (1)', + 'Too many members or infinite loop in membersp iteration (3)', ]: Suppressed[s] = True