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..d0c6dab9e 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,60 @@ 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 + .. _line coverage: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8393ec3f8..5675130c5 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 V3DebugBisect.h V3Delayed.h @@ -237,6 +238,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 00ae0273c..b1384d69f 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..011aa387c 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -619,11 +619,169 @@ public: ~ActiveVisitor() override = default; }; +//###################################################################### +// Shared state for covergroup sampling passes + +struct CovergroupState final { + std::unordered_map m_sampleFuncs; // Class -> sample CFunc + std::unordered_map + m_samplingEvents; // Class -> owned sampling event (if any) +}; + +//###################################################################### +// Pass 1: collect sample CFuncs and sampling events from covergroup class scopes + +class CovergroupCollectVisitor final : public VNVisitor { + // STATE + CovergroupState& m_state; + AstClass* m_classp = nullptr; // Current covergroup class context, or nullptr + + // VISITORS + void visit(AstClass* nodep) override { + if (!nodep->isCovergroup()) return; + VL_RESTORER(m_classp); + m_classp = nodep; + iterateChildren(nodep); + } + + void visit(AstScope* nodep) override { + if (AstClass* const classp = VN_CAST(nodep->modp(), Class)) { + if (classp->isCovergroup()) { + VL_RESTORER(m_classp); + m_classp = classp; + iterateChildren(nodep); + return; + } + } + iterateChildren(nodep); + } + + void visit(AstCFunc* nodep) override { + if (!m_classp) return; + if (nodep->name().find("sample") != string::npos) { + m_state.m_sampleFuncs[m_classp] = nodep; + nodep->isCovergroupSample(true); + } + } + + void visit(AstCovergroup* nodep) override { + // V3Covergroup guarantees: only supported-event covergroups survive to V3Active, + // and they are always inside a covergroup class (so m_classp is set). + // Unlink eventp from cgp so it survives cgp's deletion, + // then take ownership in the map for use during the second pass. + m_state.m_samplingEvents[m_classp] = nodep->eventp()->unlinkFrBack(); + nodep->unlinkFrBack(); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + CovergroupCollectVisitor(AstNetlist* nodep, CovergroupState& state) + : m_state{state} { + iterate(nodep); + } + ~CovergroupCollectVisitor() override = default; +}; + +//###################################################################### +// Pass 2: inject automatic sample() calls for covergroup instances + +class CovergroupInjectVisitor final : public VNVisitor { + // STATE + CovergroupState& m_state; + ActiveNamer m_namer; // Reuse active naming infrastructure + + // VISITORS + void visit(AstScope* nodep) override { + m_namer.main(nodep); // Initialize active naming for this scope + iterateChildren(nodep); + } + + void visit(AstVarScope* nodep) override { + // Get the underlying var + AstVar* const varp = nodep->varp(); + UASSERT_OBJ(varp, nodep, "AstVarScope must have non-null varp"); + + // Check if the variable is of covergroup class type + const AstNodeDType* const dtypep = varp->dtypep(); + UASSERT_OBJ(dtypep, nodep, "AstVar must have non-null dtypep after V3Width"); + + 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 + const auto evtIt = m_state.m_samplingEvents.find(classp); + if (evtIt == m_state.m_samplingEvents.end()) + return; // No automatic sampling for this covergroup + AstSenTree* const eventp = evtIt->second; + + // V3Covergroup guarantees every supported-event covergroup has a registered sample CFunc + const auto it = m_state.m_sampleFuncs.find(classp); + UASSERT_OBJ(it != m_state.m_sampleFuncs.end(), nodep, + "No sample() CFunc found for covergroup " << classp->name()); + AstCFunc* const sampleCFuncp = it->second; + + // 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}; + + cmethodCallp->dtypeSetVoid(); + cmethodCallp->argTypes("vlSymsp"); + + // Clone the sensitivity for this active block. + // V3Scope has already resolved all VarRefs in eventp, so the clone + // inherits correct varScopep values with no fixup needed. + AstSenTree* senTreep = eventp->cloneTree(false); + + // 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); + + // Wrap the sample() call in an AstAlways so SchedPartition handles it + // via visit(AstNodeProcedure*) like any other clocked always block. + activep->addStmtsp( + new AstAlways{fl, VAlwaysKwd::ALWAYS_FF, nullptr, cmethodCallp->makeStmt()}); + + UINFO(4, " Added automatic sample() call for covergroup " + << varp->name()); // LCOV_EXCL_BR_LINE + } + + void visit(AstActive*) override {} // Don't iterate into actives + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + CovergroupInjectVisitor(AstNetlist* nodep, CovergroupState& state) + : m_state{state} { + iterate(nodep); + } + ~CovergroupInjectVisitor() override = default; +}; + //###################################################################### // Active class functions void V3Active::activeAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { ActiveVisitor{nodep}; } // Destruct before checking + if (v3Global.useCovergroup()) { + // Add automatic covergroup sampling in two focused passes + CovergroupState state; + CovergroupCollectVisitor{nodep, state}; // Pass 1: collect CFuncs and events + CovergroupInjectVisitor{nodep, state}; // Pass 2: inject sample() calls + for (const auto& itpair : state.m_samplingEvents) itpair.second->deleteTree(); + } V3Global::dumpCheckGlobalTree("active", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 6a81c993c..5dc695faf 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1107,6 +1107,113 @@ inline std::ostream& operator<<(std::ostream& os, const VCastable& rhs) { //###################################################################### +class VCoverBinsType final { +public: + enum en : uint8_t { + BINS_USER, // Single bin with one or more values/ranges + BINS_ARRAY, // Array of bins with user-speciifed size + BINS_AUTO, // Auto-sized array of bins (eg auto_bin_max) + BINS_IGNORE, // Ignore bin + BINS_ILLEGAL, // Illegal bin + BINS_DEFAULT, // Default bin + BINS_WILDCARD, // Wildcard bin + BINS_TRANSITION // Transition bin + }; + enum en m_e; + VCoverBinsType() + : m_e{BINS_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 3a4ebf035..677255d3b 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -721,6 +721,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 decaddbb7..e9f8e5f8b 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -253,6 +253,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: @@ -513,6 +527,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 = "") @@ -543,6 +558,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; @@ -618,6 +634,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 { @@ -1020,6 +1038,142 @@ public: bool isPredictOptimizable() const override { return false; } bool sameNode(const AstNode* /*samep*/) const override { return true; } }; + +class AstCoverBin final : public AstNode { + // Captures data for a coverpoint 'bins' declaration + // @astgen op1 := rangesp : List[AstNode] + // @astgen op2 := iffp : Optional[AstNodeExpr] + // @astgen op3 := arraySizep : Optional[AstNodeExpr] + // @astgen op4 := transp : List[AstCoverTransSet] + const string m_name; // Base name of the bin + const VCoverBinsType m_type; // Bin type (eg AUTO, IGNORE, ILLEGAL) + bool m_isArray = false; // Bin is either an auto-sized array of values or transitions + +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::BINS_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::BINS_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, + VCoverBinsType type = VCoverBinsType::BINS_TRANSITION, bool isArrayBin = false) + : ASTGEN_SUPER_CoverBin(fl) + , m_name{name} + , m_type{type} + , m_isArray{isArrayBin} { + UASSERT(transp, "AstCoverBin transition constructor requires non-null 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 AstCoverOption final : public AstNode { + // Coverage-option assignment + // @astgen op1 := valuep : AstNodeExpr + const VCoverOptionType m_type; // Option being assigned + +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 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] + const 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; +}; +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 { + // Represents a covergroup declaration. V3LinkParse transforms this + // into an AstClass with isCovergroup==true and attaches the clocking + // event as a new AstCovergroup sentinel in class membersp. + // @astgen op1 := argsp : List[AstVar] + // @astgen op2 := membersp : List[AstNode] + // @astgen op3 := eventp : Optional[AstSenTree] + // @astgen op4 := sampleArgsp : List[AstVar] + string m_name; // covergroup name + +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; + string name() const override VL_MT_STABLE { return m_name; } + void name(const string& name) override { m_name = name; } + bool maybePointedTo() const override { return true; } +}; +class AstCoverpointRef final : public AstNode { + // Reference to a coverpoint used in a cross + const string m_name; // coverpoint 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; } +}; class AstDefParam final : public AstNode { // A defparam assignment // Parents: MODULE @@ -2535,6 +2689,39 @@ 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 := optionsp : List[AstCoverOption] // post-LinkParse only + // @astgen op3 := rawBodyp : List[AstNode] // Parse: raw cross_body items; + // // post-LinkParse: empty +public: + AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp) + : ASTGEN_SUPER_CoverCross(fl, name) { + UASSERT(itemsp, "AstCoverCross requires at least one coverpoint reference"); + 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[AstNode] // Parse: mixed AstCoverBin/AstCgOptionAssign; + // post-LinkParse: AstCoverBin only + // @astgen op3 := iffp : Optional[AstNodeExpr] + // @astgen op4 := optionsp : List[AstCoverOption] +public: + AstCoverpoint(FileLine* fl, const string& name, AstNodeExpr* exprp, + AstNodeExpr* iffp = nullptr, AstNode* binsp = nullptr) + : ASTGEN_SUPER_Coverpoint(fl, name) { + this->exprp(exprp); + this->iffp(iffp); + if (binsp) addBinsp(binsp); + } + ASTGEN_MEMBERS_AstCoverpoint; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; +}; // === AstNodeGen === class AstGenBlock final : public AstNodeGen { @@ -2617,6 +2804,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) @@ -2644,6 +2833,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 5465f876a..c613e3760 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3495,3 +3495,54 @@ const char* AstNot::widthMismatch() const VL_MT_STABLE { BROKEN_RTN(lhsp()->widthMin() != widthMin()); return nullptr; } + +//###################################################################### +// Functional coverage dump methods + +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_type.ascii(); + if (m_isArray) str << "[]"; +} + +void AstCoverBin::dumpJson(std::ostream& str) const { + this->AstNode::dumpJson(str); + 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); } + +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 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); } + +void AstCoverpointRef::dumpJson(std::ostream& str) const { this->AstNode::dumpJson(str); } diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 7f25d6b5b..d5ebea939 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..caa52a545 --- /dev/null +++ b/src/V3Covergroup.cpp @@ -0,0 +1,1563 @@ +// -*- 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 "V3Const.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::map m_coverpointMap; // Name -> coverpoint for fast lookup + 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 + + 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) pushDeletep(bi.binp); + } + m_binInfos.clear(); + } + + void processCovergroup() { + UINFO(4, "Processing covergroup: " << m_covergroupp->name() << " with " + << m_coverpoints.size() << " coverpoints and " + << m_coverCrosses.size() << " crosses"); + + // 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_AS(binp, CoverBin); + AstNode* const nextBinp = binp->nextp(); + + if (cbinp->binsType() == VCoverBinsType::BINS_AUTO) { + UINFO(4, " Expanding automatic bin: " << cbinp->name()); + + // Get array size - must be a constant + AstNodeExpr* const sizep = cbinp->arraySizep(); + + // 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) { + cbinp->v3error("Automatic bins array size must be >= 1, 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); + // For width >= 64: (maxVal+1) would overflow; compute binSize without overflow + const uint64_t binSize + = (width < 64) ? ((maxVal + 1) / numBins) : (UINT64_MAX / numBins + 1); + + UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins + << " binSize=" << binSize); + + // Create expanded bins + for (int i = 0; i < numBins; i++) { + const uint64_t lo = static_cast(i) * binSize; + const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1); + + // Create constants for range (use setQuad to handle values > 32-bit) + V3Number loNum{cbinp->fileline(), width, 0}; + loNum.setQuad(lo); + AstConst* const loConstp = new AstConst{cbinp->fileline(), loNum}; + V3Number hiNum{cbinp->fileline(), width, 0}; + hiNum.setQuad(hi); + AstConst* const hiConstp = new AstConst{cbinp->fileline(), hiNum}; + + // Create InsideRange [lo:hi] + AstInsideRange* const 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* const 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 + VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp); + } else { + prevBinp = binp; + } + + binp = nextBinp; + } + } + + // Extract all coverpoint option values in a single pass. + // atLeastOut: option.at_least (default 1) + // autoBinMaxOut: option.auto_bin_max (coverpoint overrides covergroup, default 64) + void extractCoverpointOptions(AstCoverpoint* coverpointp, int& atLeastOut, + int& autoBinMaxOut) { + atLeastOut = 1; + autoBinMaxOut = -1; // -1 = not set at coverpoint level + for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) { + AstCoverOption* const optp = VN_AS(optionp, CoverOption); + AstConst* const constp = VN_AS(optp->valuep(), Const); + if (optp->optionType() == VCoverOptionType::AT_LEAST) { + atLeastOut = constp->toSInt(); + } else if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) { + autoBinMaxOut = constp->toSInt(); + } + } + // Fall back to covergroup-level auto_bin_max if not set at coverpoint level + if (autoBinMaxOut < 0) { + if (m_covergroupp->cgAutoBinMax() >= 0) { + autoBinMaxOut = m_covergroupp->cgAutoBinMax(); + } else { + autoBinMaxOut = 64; // Default per IEEE 1800-2017 + } + } + } + + // Extract individual values from a range expression list. + // Iterates over all siblings (nextp) in the list, handling AstConst (single value) + // and AstInsideRange ([lo:hi]). + void extractValuesFromRange(AstNode* nodep, std::set& values) { + for (AstNode* np = nodep; np; np = np->nextp()) { + if (AstConst* constp = VN_CAST(np, Const)) { + values.insert(constp->toUQuad()); + } else if (AstInsideRange* rangep = VN_CAST(np, InsideRange)) { + AstNodeExpr* const lhsp = V3Const::constifyEdit(rangep->lhsp()); + AstNodeExpr* const rhsp = V3Const::constifyEdit(rangep->rhsp()); + AstConst* const loConstp = VN_AS(lhsp, Const); + AstConst* const hiConstp = VN_AS(rhsp, Const); + const uint64_t lo = loConstp->toUQuad(); + const uint64_t hi = hiConstp->toUQuad(); + for (uint64_t v = lo; v <= hi; v++) values.insert(v); + } else { + np->v3error("Non-constant expression in bin value list; values must be constants"); + } + } + } + + // Single-pass categorization: determine whether any regular (non-ignore/illegal) bins exist + // and collect the set of excluded values from ignore/illegal bins. + void categorizeBins(AstCoverpoint* coverpointp, bool& hasRegularOut, + std::set& excludedOut) { + hasRegularOut = false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + const VCoverBinsType btype = cbinp->binsType(); + if (btype == VCoverBinsType::BINS_IGNORE || btype == VCoverBinsType::BINS_ILLEGAL) { + if (AstNode* rangep = cbinp->rangesp()) { + extractValuesFromRange(rangep, excludedOut); + } + } else { + hasRegularOut = true; + } + } + } + + // Create implicit automatic bins when coverpoint has no explicit regular bins + void createImplicitAutoBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp, int autoBinMax) { + // Single pass: check for regular bins and collect excluded values simultaneously + bool hasRegular = false; + std::set excluded; + categorizeBins(coverpointp, hasRegular, excluded); + + // If already has regular bins, nothing to do + if (hasRegular) return; + + UINFO(4, " Creating implicit automatic bins for coverpoint: " << coverpointp->name()); + + if (!excluded.empty()) { UINFO(4, " Found " << excluded.size() << " excluded values"); } + + 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"); + + // 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* const valConstp = new AstConst{ + coverpointp->fileline(), V3Number(coverpointp->fileline(), width, v)}; + AstConst* const valConstp2 = new AstConst{ + coverpointp->fileline(), V3Number(coverpointp->fileline(), width, v)}; + + AstInsideRange* const rangep + = new AstInsideRange{coverpointp->fileline(), valConstp, valConstp2}; + rangep->dtypeFrom(exprp); + + const string binName = "auto_" + std::to_string(binCount); + AstCoverBin* const newBinp + = new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false}; + + coverpointp->addBinsp(newBinp); + binCount++; + } + UINFO(4, " Created " << binCount << " single-value automatic bins"); + } 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); + + // Create constants for range + AstConst* const loConstp = new AstConst{ + coverpointp->fileline(), V3Number(coverpointp->fileline(), width, lo)}; + AstConst* const hiConstp = new AstConst{ + coverpointp->fileline(), V3Number(coverpointp->fileline(), width, hi)}; + + // Create InsideRange [lo:hi] + AstInsideRange* const rangep + = new AstInsideRange{coverpointp->fileline(), loConstp, hiConstp}; + rangep->dtypeFrom(exprp); + + // Create bin name + const string binName = "auto_" + std::to_string(i); + AstCoverBin* const newBinp + = new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false}; + + // Add to coverpoint + coverpointp->addBinsp(newBinp); + } + + UINFO(4, " Created range-based automatic bins"); + } + } + + // Create previous value variable for transition tracking + AstVar* createPrevValueVar(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { + // Check if already created + const 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); + + // Initialize to zero in constructor + AstNodeExpr* const initExprp + = new AstConst{prevVarp->fileline(), AstConst::WidthedValue{}, prevVarp->width(), 0}; + AstNodeStmt* const 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) { + // 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); + + // Initialize to 0 (not started) in constructor + AstNodeStmt* const initStmtp = new AstAssign{ + stateVarp->fileline(), new AstVarRef{stateVarp->fileline(), stateVarp, VAccess::WRITE}, + new AstConst{stateVarp->fileline(), AstConst::WidthedValue{}, 8, 0}}; + m_constructorp->addStmtsp(initStmtp); + + return stateVarp; + } + + void generateCoverpointCode(AstCoverpoint* coverpointp) { + UINFO(4, " Generating code for coverpoint: " << coverpointp->name()); + + // Get the coverpoint expression + AstNodeExpr* const exprp = coverpointp->exprp(); + + // Expand automatic bins before processing + expandAutomaticBins(coverpointp, exprp); + + // Extract all coverpoint options in a single pass + int atLeastValue; + int autoBinMax; + extractCoverpointOptions(coverpointp, atLeastValue, autoBinMax); + UINFO(6, " Coverpoint at_least = " << atLeastValue << " auto_bin_max = " << autoBinMax); + + // Create implicit automatic bins if no regular bins exist + createImplicitAutoBins(coverpointp, exprp, autoBinMax); + + // Generate member variables and matching code for each bin + // Process in two passes: first non-default bins, then default bins + std::vector defaultBins; + bool hasTransition = false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + + // Defer default bins to second pass + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + + // Handle array bins: create separate bin for each value/transition + if (cbinp->isArray()) { + if (cbinp->transp()) { // transition bin (includes illegal_bins with transitions) + hasTransition = true; + 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); + varp->valuep(new AstConst{cbinp->fileline(), AstConst::WidthedValue{}, 32, 0}); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created member variable: " << varName + << " type=" << cbinp->binsType().ascii()); + + // 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 (includes illegal_bins with transition syntax) + if (cbinp->transp()) { + hasTransition = true; + 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); + varp->valuep(new AstConst{defBinp->fileline(), AstConst::WidthedValue{}, 32, 0}); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created default bin variable: " << varName); + + // 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 (hasTransition) { + AstVar* const 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()"); + } + } + + void generateBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, AstNodeExpr* exprp, + AstVar* hitVarp) { + UINFO(4, " Generating bin match for: " << binp->name()); + + // Build the bin matching condition using the shared function + AstNodeExpr* fullCondp = buildBinCondition(binp, exprp); + + if (!fullCondp) { + // Reachable: e.g. 'ignore_bins ib = default' creates a BINS_IGNORE bin + // with null rangesp. Skipping match code generation is correct in that case. + return; + } + + // Apply iff condition if present - wraps the bin match condition + if (AstNodeExpr* iffp = coverpointp->iffp()) { + UINFO(6, " Adding iff condition"); + fullCondp = new AstAnd{binp->fileline(), iffp->cloneTree(false), fullCondp}; + } + + // Create the increment statement + AstNode* stmtp = makeBinHitIncrement(binp->fileline(), hitVarp); + + // 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() + "'"; + stmtp = stmtp->addNext(makeIllegalBinAction(binp->fileline(), errMsg)); + } + + // 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"); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating bin match code"); + m_sampleFuncp->addStmtsp(ifp); + UINFO(4, " Successfully added if statement for bin: " << binp->name()); + } + + // 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()); + + // 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_AS(binp, CoverBin); + + // Skip default, ignore, and illegal bins + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + continue; + } + + // Build condition for this bin + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + + // 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* const stmtp = makeBinHitIncrement(defBinp->fileline(), hitVarp); + + // Create if statement + AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr}; + + UASSERT_OBJ(m_sampleFuncp, defBinp, + "sample() CFunc not set when generating default bin code"); + m_sampleFuncp->addStmtsp(ifp); + UINFO(4, " Successfully added default bin if statement"); + } + + // 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()); + + // Get the (single) transition set + AstCoverTransSet* const transSetp = binp->transp(); + + // 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()); + UINFO(4, " Sequence length: " << items.size() << " items"); + + // Create state position variable + AstVar* const 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* const 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); + casep->addItemsp(caseItemp); + } + + // Add default case (reset to state 0) to prevent CASEINCOMPLETE warnings, + // since the state variable is wider than the number of valid states. + AstCaseItem* const defaultItemp = new AstCaseItem{ + binp->fileline(), nullptr, + new AstAssign{binp->fileline(), + new AstVarRef{binp->fileline(), stateVarp, VAccess::WRITE}, + new AstConst{binp->fileline(), AstConst::WidthedValue{}, 8, 0}}}; + casep->addItemsp(defaultItemp); + + m_sampleFuncp->addStmtsp(casep); + UINFO(4, " Successfully added multi-value transition state machine"); + } + + // 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); + + // 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 = makeBinHitIncrement(fl, hitVarp); + + // For illegal_bins, add error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal transition bin " + binp->prettyNameQ() + + " hit in coverpoint " + coverpointp->prettyNameQ(); + matchActionp = matchActionp->addNext(makeIllegalBinAction(fl, errMsg)); + } + + // 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); + + UASSERT_OBJ(restartCondp, items[0], + "buildTransitionItemCondition returned nullptr for restart"); + // 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}; + } + // For state 0, no action needed if no match (stay in state 0) + + // Combine into if-else + AstNodeStmt* const stmtp = new AstIf{fl, matchCondp, matchActionp, noMatchActionp}; + + // Create case item for this state value + AstCaseItem* const caseItemp = new AstCaseItem{ + fl, new AstConst{fl, AstConst::WidthedValue{}, 8, static_cast(state)}, + stmtp}; + + return caseItemp; + } + + // Create: $error(msg); $stop; Used when an illegal bin is hit. + AstNodeStmt* makeIllegalBinAction(FileLine* fl, const string& errMsg) { + AstDisplay* const errorp + = new AstDisplay{fl, VDisplayType::DT_ERROR, errMsg, nullptr, nullptr}; + errorp->fmtp()->timeunit(m_covergroupp->timeunit()); + static_cast(errorp)->addNext(new AstStop{fl, true}); + return errorp; + } + + // Create: hitVarp = hitVarp + 1 + AstAssign* makeBinHitIncrement(FileLine* fl, AstVar* hitVarp) { + return 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}}}; + } + + // Clone a constant node, widening to targetWidth if needed (zero-extend). + // Used to ensure comparisons use matching widths after V3Width has run. + static AstConst* widenConst(FileLine* fl, AstConst* constp, int targetWidth) { + if (constp->width() == targetWidth) return constp->cloneTree(false); + V3Number num{fl, targetWidth, 0}; + num.opAssign(constp->num()); + return new AstConst{fl, num}; + } + + // Build a range condition: minp <= exprp <= maxp. + // Uses signed comparisons if exprp is signed; omits trivially-true bounds for unsigned. + // All arguments are non-owning; clones exprp/minp/maxp as needed. + AstNodeExpr* makeRangeCondition(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* minp, + AstNodeExpr* maxp) { + const int exprWidth = exprp->widthMin(); + AstConst* const minConstp = VN_AS(minp, Const); + AstConst* const maxConstp = VN_AS(maxp, Const); + // Widen constants to match expression width so post-V3Width nodes use correct macros + AstConst* const minWidep = widenConst(fl, minConstp, exprWidth); + AstConst* const maxWidep = widenConst(fl, maxConstp, exprWidth); + if (exprp->isSigned()) { + return new AstAnd{fl, new AstGteS{fl, exprp->cloneTree(false), minWidep}, + new AstLteS{fl, exprp->cloneTree(false), maxWidep}}; + } + // Unsigned: skip bounds that are trivially satisfied for the expression width + const bool skipLowerCheck = (minConstp->toUQuad() == 0); + bool skipUpperCheck = false; + if (exprWidth <= 64) { + const uint64_t maxVal + = (exprWidth == 64) ? ~static_cast(0) : ((1ULL << exprWidth) - 1ULL); + skipUpperCheck = (maxConstp->toUQuad() == maxVal); + } + if (skipLowerCheck && skipUpperCheck) { + VL_DO_DANGLING(pushDeletep(minWidep), minWidep); + VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep); + return new AstConst{fl, AstConst::BitTrue{}}; + } else if (skipLowerCheck) { + VL_DO_DANGLING(pushDeletep(minWidep), minWidep); + return new AstLte{fl, exprp->cloneTree(false), maxWidep}; + } else if (skipUpperCheck) { + VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep); + return new AstGte{fl, exprp->cloneTree(false), minWidep}; + } else { + return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minWidep}, + new AstLte{fl, exprp->cloneTree(false), maxWidep}}; + } + } + + // Build condition for a single transition item. + // Returns expression that checks if exprp matches the item's value/range list. + // Overload for when the expression is a variable read -- creates and manages the VarRef + // internally, so callers don't need to construct a temporary node. + AstNodeExpr* buildTransitionItemCondition(AstCoverTransItem* itemp, AstVar* varp) { + AstNodeExpr* varRefp = new AstVarRef{varp->fileline(), varp, VAccess::READ}; + AstNodeExpr* const condp = buildTransitionItemCondition(itemp, varRefp); + VL_DO_DANGLING(pushDeletep(varRefp), varRefp); + return condp; + } + + // Non-owning: exprp is cloned internally; caller retains ownership of exprp. + AstNodeExpr* buildTransitionItemCondition(AstCoverTransItem* itemp, AstNodeExpr* exprp) { + AstNodeExpr* condp = nullptr; + + for (AstNode* valp = itemp->valuesp(); valp; valp = valp->nextp()) { + AstNodeExpr* singleCondp = nullptr; + + AstConst* const constp = VN_AS(valp, Const); + singleCondp + = new AstEq{constp->fileline(), exprp->cloneTree(false), constp->cloneTree(false)}; + + if (condp) { + condp = new AstOr{itemp->fileline(), condp, singleCondp}; + } else { + condp = singleCondp; + } + } + + 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()); + + // Extract all values from the range list + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstInsideRange* const insideRangep = VN_CAST(rangep, InsideRange)) { + // For InsideRange [min:max], create bins for each value + AstNodeExpr* const minp = V3Const::constifyEdit(insideRangep->lhsp()); + AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp()); + AstConst* const minConstp = VN_CAST(minp, Const); + AstConst* const maxConstp = VN_CAST(maxp, Const); + if (minConstp && maxConstp) { // LCOV_EXCL_BR_LINE + const int minVal = minConstp->toSInt(); + const int maxVal = maxConstp->toSInt(); + UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]"); + for (int val = minVal; val <= maxVal; ++val) { + values.push_back(new AstConst{insideRangep->fileline(), + AstConst::WidthedValue{}, + (int)exprp->width(), (uint32_t)val}); + } + } else { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants"); + 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); + varp->valuep(new AstConst{arrayBinp->fileline(), AstConst::WidthedValue{}, 32, 0}); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created array bin [" << index << "]: " << varName); + + // 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"); + } + + // 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 = makeBinHitIncrement(binp->fileline(), hitVarp); + + // For illegal_bins, add error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal bin hit in coverpoint '" + coverpointp->name() + "'"; + stmtp = stmtp->addNext(makeIllegalBinAction(binp->fileline(), errMsg)); + } + + // Create if statement + AstIf* const ifp = new AstIf{binp->fileline(), condp, stmtp, nullptr}; + + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating array bin code"); + 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()); + + // Extract all transition sets + std::vector transSets; + for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp()) + transSets.push_back(VN_AS(transSetp, CoverTransSet)); + + UINFO(4, " Found " << transSets.size() << " transition sets"); + 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); + varp->valuep(new AstConst{arrayBinp->fileline(), AstConst::WidthedValue{}, 32, 0}); + m_covergroupp->addMembersp(varp); + UINFO(4, " Created transition array bin [" << index << "]: " << varName); + + // 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"); + } + + // 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"); + + // Get or create previous value variable + AstVar* const prevVarp = createPrevValueVar(coverpointp, exprp); + + UASSERT_OBJ( + transSetp, binp, + "Transition bin has no transition set (transp() was checked before calling this)"); + + // Get transition items (the sequence: item1 => item2 => item3) + std::vector items; + for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp()) + items.push_back(VN_AS(itemp, CoverTransItem)); + + if (items.empty()) { + binp->v3error("Transition set without items"); + return; + } + + 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* const cond1p = buildTransitionItemCondition(items[0], prevVarp); + AstNodeExpr* const cond2p = buildTransitionItemCondition(items[1], exprp); + + // 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 = makeBinHitIncrement(binp->fileline(), hitVarp); + + // For illegal_bins, add an error message + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + const string errMsg = "Illegal transition bin " + binp->prettyNameQ() + + " hit in coverpoint " + coverpointp->prettyNameQ(); + stmtp = stmtp->addNext(makeIllegalBinAction(binp->fileline(), errMsg)); + } + + // 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"); + } 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); + varp->valuep(new AstConst{crossp->fileline(), AstConst::WidthedValue{}, 32, 0}); + m_covergroupp->addMembersp(varp); + + UINFO(4, " Created cross bin variable: " << varName); + + // Track this for coverage computation + AstCoverBin* const 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"); + + // Build combined condition by ANDing all bin conditions + AstNodeExpr* fullCondp = nullptr; + + for (size_t i = 0; i < bins.size(); ++i) { + AstNodeExpr* const exprp = coverpointRefs[i]->exprp(); + AstNodeExpr* const condp = buildBinCondition(bins[i], exprp); + + if (fullCondp) { + fullCondp = new AstAnd{crossp->fileline(), fullCondp, condp}; + } else { + fullCondp = condp; + } + } + + // Generate: if (cond1 && cond2 && ... && condN) { ++varName; } + AstNodeStmt* const incrp = makeBinHitIncrement(crossp->fileline(), hitVarp); + + AstIf* const ifp = new AstIf{crossp->fileline(), fullCondp, incrp}; + m_sampleFuncp->addStmtsp(ifp); + } + + void generateCrossCode(AstCoverCross* crossp) { + UINFO(4, " Generating code for cross: " << crossp->name()); + + // Resolve coverpoint references and build list + std::vector coverpointRefs; + AstNode* itemp = crossp->itemsp(); + while (itemp) { + AstNode* const nextp = itemp->nextp(); + AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + // Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan) + const auto it = m_coverpointMap.find(refp->name()); + AstCoverpoint* const foundCpp = (it != m_coverpointMap.end()) ? it->second : nullptr; + + if (!foundCpp) { + // Name not found as an explicit coverpoint - it's likely a direct variable + // reference (implicit coverpoint). Silently ignore; cross is dropped. + UINFO(4, " Ignoring cross with implicit variable reference: " << refp->name()); + return; + } + + coverpointRefs.push_back(foundCpp); + + // Delete the reference node - it's no longer needed + VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp); + itemp = nextp; + } // LCOV_EXCL_BR_LINE + + UINFO(4, " Generating " << coverpointRefs.size() << "-way cross"); + + // 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_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_USER) { cpBins.push_back(cbinp); } + } + UINFO(4, " Found " << cpBins.size() << " bins in " << cpp->name()); + 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* const rangep = binp->rangesp(); + if (!rangep) return nullptr; + + // Check if this is a wildcard bin + const 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* rangeCondp = nullptr; + + if (AstInsideRange* irp = VN_CAST(currRangep, InsideRange)) { + AstNodeExpr* const minExprp = irp->lhsp(); + AstNodeExpr* const maxExprp = irp->rhsp(); + AstConst* const minConstp = VN_AS(minExprp, Const); + AstConst* const maxConstp = VN_AS(maxExprp, Const); + if (minConstp->toUQuad() == maxConstp->toUQuad()) { + // Single value + if (isWildcard) { + rangeCondp = buildWildcardCondition(binp, exprp, minConstp); + } else { + rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false), + minExprp->cloneTree(false)}; + } + } else { + rangeCondp = makeRangeCondition(irp->fileline(), exprp, minExprp, maxExprp); + } + } else if (AstConst* constp = VN_CAST(currRangep, Const)) { + if (isWildcard) { + rangeCondp = buildWildcardCondition(binp, exprp, constp); + } else { + rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false), + constp->cloneTree(false)}; + } + } else { + currRangep->v3error( + "Non-constant expression in bin range; values must be constants"); + return nullptr; + } + + UASSERT_OBJ(rangeCondp, binp, "rangeCondp is null after building range condition"); + 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 + // Non-owning: exprp is cloned internally; caller retains ownership. + AstNodeExpr* buildWildcardCondition(AstCoverBin* binp, AstNodeExpr* exprp, AstConst* constp) { + FileLine* const 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 (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* const maskConstp = new AstConst{fl, mask}; + AstConst* const valueConstp = new AstConst{fl, value}; + + AstNodeExpr* const exprMasked = new AstAnd{fl, exprp->cloneTree(false), maskConstp}; + AstNodeExpr* const valueMasked = new AstAnd{fl, valueConstp, maskConstp->cloneTree(false)}; + + return new AstEq{fl, exprMasked, valueMasked}; + } + + void generateCoverageComputationCode() { + UINFO(4, " Generating coverage computation code"); + + // 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); + + // 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%"); + } else { + UINFO(6, " Found " << m_binInfos.size() << " bins for coverage"); + } + + // Generate code for get_inst_coverage() + 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 + AstVar* const coverageReturnVarp = VN_AS(getCoveragep->fvarp(), Var); + // 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(), coverageReturnVarp, VAccess::WRITE}, + new AstConst{getCoveragep->fileline(), AstConst::RealDouble{}, 0.0}}); + UINFO(4, " Added placeholder get_coverage() (returns 0.0)"); + } + + void generateCoverageMethodBody(AstFunc* funcp) { + FileLine* const 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=" << bi.binp->binsType().ascii()); + if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE + && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { + totalBins++; + } + } + + UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); + + if (totalBins == 0) { + // No coverage to compute - return 100%. + // Any parser-generated initialization of returnVar is overridden by our assignment. + UINFO(4, " Empty covergroup, returning 100.0"); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); + funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::RealDouble{}, 100.0}}); + UINFO(4, " Added assignment to return 100.0"); + return; + } + + // Create local variable to count covered bins + AstVar* const 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* const returnVarp = VN_AS(funcp->fvarp(), Var); + + // 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* const coveredReal + = new AstIToRD{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}}; + + // Create total bins as a double constant + AstNodeExpr* const totalReal + = new AstConst{fl, AstConst::RealDouble{}, static_cast(totalBins)}; + + // Divide using AstDivD (double division that emits native /) + AstNodeExpr* const divExpr = new AstDivD{fl, coveredReal, totalReal}; + + // Multiply by 100 using AstMulD (double multiplication that emits native *) + AstNodeExpr* const hundredConst = new AstConst{fl, AstConst::RealDouble{}, 100.0}; + AstNodeExpr* const 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)"); + } + + 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"); + + if (m_binInfos.empty()) 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* const varp = binInfo.varp; + AstCoverBin* const binp = binInfo.binp; + AstCoverpoint* const coverpointp = binInfo.coverpointp; + AstCoverCross* const crossp = binInfo.crossp; + + FileLine* const fl = binp->fileline(); + + // Build hierarchical name: covergroup.coverpoint.bin or covergroup.cross.bin + std::string hierName = m_covergroupp->name(); + const std::string binName = binp->name(); + + if (coverpointp) { + // Coverpoint bin: use coverpoint name or generate from expression + std::string cpName = coverpointp->name(); + if (cpName.empty()) { + // Unlabeled coverpoint: name comes from its expression (always a VarRef) + UASSERT_OBJ(coverpointp->exprp(), coverpointp, + "Coverpoint without expression and without name"); + cpName = coverpointp->exprp()->name(); + UASSERT_OBJ(!cpName.empty(), coverpointp, + "Coverpoint expression has empty name"); + } + hierName += "." + cpName; + } else { + // Cross bin: grammar always provides a name (user label or auto "__crossN") + hierName += "." + crossp->name(); + } + hierName += "." + binName; + + // Generate: VL_COVER_INSERT(contextp, hier, &binVar, "page", "v_covergroup/...", ...) + + UINFO(6, " Registering bin: " << hierName << " -> " << varp->name()); + + // Build the coverage insert as a C statement mixing literal text with a proper + // AstVarRef for the bin variable. Using AstVarRef (with selfPointer=This) lets + // V3Name apply __PVT__ mangling and the emitter apply nameProtect(), which also + // handles --protect-ids correctly. The vlSymsp->_vm_contextp__ path is the + // established convention used by the existing __vlCoverInsert helper. + // Use "page" field with v_covergroup prefix so the coverage type is identified + // correctly (consistent with code coverage). + const std::string pageName = "v_covergroup/" + m_covergroupp->name(); + AstCStmt* const cstmtp = new AstCStmt{fl}; + cstmtp->add("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), " + "\"" + + hierName + "\", &("); + AstVarRef* const binVarRefp = new AstVarRef{fl, varp, VAccess::READ}; + binVarRefp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); + cstmtp->add(binVarRefp); + cstmtp->add("), \"page\", \"" + pageName + + "\", " + "\"filename\", \"" + + fl->filename() + + "\", " + "\"lineno\", \"" + + std::to_string(fl->lineno()) + + "\", " + "\"column\", \"" + + std::to_string(fl->firstColumn()) + "\", "); + const std::string crossSuffix = crossp ? ", \"cross\", \"1\"" : ""; + if (binp->binsType() == VCoverBinsType::BINS_IGNORE) { + cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"ignore\"" + crossSuffix + + ");"); + } else if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"illegal\"" + crossSuffix + + ");"); + } else { + cstmtp->add("\"bin\", \"" + binName + "\"" + crossSuffix + ");"); + } + + // Add to constructor + m_constructorp->addStmtsp(cstmtp); + + UINFO(6, " Added VL_COVER_INSERT call to constructor"); + } + } + + // VISITORS + void visit(AstClass* nodep) override { + UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup()); + if (nodep->isCovergroup()) { + VL_RESTORER(m_covergroupp); + m_covergroupp = nodep; + m_sampleFuncp = nullptr; + m_constructorp = nullptr; + m_coverpoints.clear(); + m_coverpointMap.clear(); + m_coverCrosses.clear(); + + // Extract and store the clocking event from AstCovergroup node + // The parser creates this node to preserve the event information + bool hasUnsupportedEvent = false; + for (AstNode* itemp = nodep->membersp(); itemp;) { + AstNode* const nextp = itemp->nextp(); + if (AstCovergroup* const cgp = VN_CAST(itemp, Covergroup)) { + // Store the event in the global map for V3Active to retrieve later + // V3LinkParse only creates this sentinel AstCovergroup node when a clocking + // event exists, so cgp->eventp() is always non-null here. + UASSERT_OBJ(cgp->eventp(), cgp, + "Sentinel AstCovergroup in class must have non-null 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()) { + AstSenItem* const senItemp = VN_AS(senp, SenItem); + if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE + = VN_CAST(senItemp->sensp(), VarRef)) { + if (varrefp->varp()->isClassMember()) { + cgp->v3warn(COVERIGN, "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()); + itemp = nextp; + continue; + } + // Remove the AstCovergroup node - either unsupported event or no event + VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), 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) { + VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); + } + for (AstCoverCross* crossp : m_coverCrosses) { + VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), 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")); + UINFO(9, "Found constructor: " << (m_constructorp ? "yes" : "no")); + + 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) { + VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); + } + for (AstCoverCross* crossp : m_coverCrosses) { + VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); + } + } else { + iterateChildren(nodep); + } + } + + void visit(AstCoverpoint* nodep) override { + UINFO(9, "Found coverpoint: " << nodep->name()); + m_coverpoints.push_back(nodep); + m_coverpointMap.emplace(nodep->name(), nodep); + iterateChildren(nodep); + } + + void visit(AstCoverCross* nodep) override { + UINFO(9, "Found cross: " << nodep->name()); + 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__ << ": "); + { 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/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 0558c35e8..b5448cffb 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -282,6 +282,13 @@ class DataflowOptimize final { if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp); return; } + // TODO: remove once Actives can tolerate NEVER SenItems + if (AstSenItem* senItemp = VN_CAST(nodep, SenItem)) { + senItemp->foreach([](const AstVarRef* refp) { + DfgVertexVar::setHasExtRdRefs(refp->varScopep()); + }); + return; + } // Check direct references if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { if (refp->access().isRW()) DfgVertexVar::setHasRWRefs(refp->varScopep()); diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index ea82d070c..59bc330d5 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -267,7 +267,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 "); @@ -307,6 +306,72 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { void visit(AstCoverInc*) override {} // N/A void visit(AstCoverToggle*) override {} // N/A + void visit(AstCovergroup* nodep) override { + // AstCovergroup appears as a member inside the lowered AstClass body. + // The outer covergroup/endcovergroup wrapper is already emitted by the + // AstNodeModule visitor (verilogKwd()="covergroup" on AstClass::isCovergroup). + // Here we only emit the clocking event, if any. + if (nodep->eventp()) { + putfs(nodep, ""); + iterateConst(nodep->eventp()); + } + } + void visit(AstCoverpoint* nodep) override { + putfs(nodep, nodep->name() + ": coverpoint "); + iterateAndNextConstNull(nodep->exprp()); + if (nodep->binsp() || nodep->optionsp()) { + puts(" {\n"); + iterateAndNextConstNull(nodep->optionsp()); + iterateAndNextConstNull(nodep->binsp()); + puts("}"); + } + puts(";\n"); + } + void visit(AstCoverBin* nodep) override { + switch (nodep->binsType()) { + case VCoverBinsType::BINS_IGNORE: putfs(nodep, "ignore_bins "); break; + case VCoverBinsType::BINS_ILLEGAL: putfs(nodep, "illegal_bins "); break; + default: putfs(nodep, "bins "); break; + } + puts(nodep->name()); + if (nodep->binsType() == VCoverBinsType::BINS_DEFAULT) { + puts(" = default"); + } else if (nodep->transp()) { + puts(" = "); + for (AstNode* setp = nodep->transp(); setp; setp = setp->nextp()) { + if (setp != nodep->transp()) puts(", "); + iterateConst(setp); + } + } else if (nodep->rangesp()) { // LCOV_EXCL_BR_LINE - false: CoverBin always has + // transp/rangesp/default + puts(" = {"); + for (AstNode* rangep = nodep->rangesp(); rangep; rangep = rangep->nextp()) { + if (rangep != nodep->rangesp()) puts(", "); + iterateConst(rangep); + } + puts("}"); + } + puts(";\n"); + } + void visit(AstCoverpointRef* nodep) override { putfs(nodep, nodep->name()); } + void visit(AstCoverCross* nodep) override { + putfs(nodep, nodep->name() + ": cross "); + for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) { + if (itemp != nodep->itemsp()) puts(", "); + iterateConst(itemp); + } + puts(";\n"); + } + void visit(AstCoverTransSet* nodep) override { + puts("("); + for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) { + if (itemp != nodep->itemsp()) puts(" => "); + iterateConst(itemp); + } + puts(")"); + } + void visit(AstCoverTransItem* nodep) override { iterateChildrenConst(nodep); } + void visit(AstCvtPackString* nodep) override { putfs(nodep, ""); if (AstConst* const lhsConstp = VN_CAST(nodep->lhsp(), Const)) { @@ -745,6 +810,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { iterateAndNextConstNull(nodep->elsep()); puts(")"); } + void visit(AstInsideRange* nodep) override { + puts("["); + iterateAndNextConstNull(nodep->lhsp()); + puts(":"); + iterateAndNextConstNull(nodep->rhsp()); + puts("]"); + } void visit(AstRange* nodep) override { puts("["); if (VN_IS(nodep->leftp(), Const) && VN_IS(nodep->rightp(), Const)) { @@ -942,6 +1014,12 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { puts("\n???? // "s + nodep->prettyTypeName() + " -> UNLINKED\n"); } } + void visit(AstClassRefDType* nodep) override { + putfs(nodep, nodep->classp() ? EmitCUtil::prefixNameProtect( + nodep->classp()) // LCOV_EXCL_BR_LINE - false: classp + // always set after linking + : nodep->prettyDTypeName(false)); + } void visit(AstRequireDType* nodep) override { iterateConst(nodep->lhsp()); } void visit(AstModport* nodep) override { puts(nodep->verilogKwd()); diff --git a/src/V3Global.h b/src/V3Global.h index 7b5c1aa18..e802ae289 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/V3LinkParse.cpp b/src/V3LinkParse.cpp index 1014e6332..c553123dd 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1118,6 +1118,263 @@ 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. + // At this point the only AstFunc in the class is the "new" constructor + // created just above; other members are AstVar or AstCovergroup sentinel. + AstFunc* newFuncp = nullptr; + for (AstNode* memberp = nodep->membersp(); memberp; memberp = memberp->nextp()) { + if (AstFunc* const funcp = VN_CAST(memberp, Func)) { + UASSERT_OBJ( + funcp->name() == "new", funcp, + "Unexpected non-new function in covergroup class during arg setup"); + newFuncp = funcp; + break; + } + } + UASSERT_OBJ(newFuncp, nodep, + "Covergroup class must have a 'new' constructor function"); + // 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()) { + AstVar* const origVarp = VN_AS(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 / type_option members allow external access (cg_inst.option.X) + // and require std:: types; std:: is kept because setUsesStdPackage() is called + // at parse time for every covergroup declaration. + { + 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); + } + { + 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()) { + AstVar* const origVarp = VN_AS(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()) { + AstVar* const origVarp = VN_AS(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()) { + AstVar* const origVarp = VN_AS(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(AstCoverpoint* nodep) override { + cleanFileline(nodep); + // Re-sort the parse-time mixed bins list (AstCoverBin + AstCgOptionAssign) + // into the typed binsp and optionsp slots. The grammar attaches both node types + // to binsp (op2) as a raw List[AstNode]; now that they are properly parented we + // can iterate and split them without any temporary-parent tricks. + for (AstNode *itemp = nodep->binsp(), *nextp; itemp; itemp = nextp) { + nextp = itemp->nextp(); + if (AstCgOptionAssign* const optp = VN_CAST(itemp, CgOptionAssign)) { + optp->unlinkFrBack(); + VCoverOptionType optType = VCoverOptionType::COMMENT; + 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->prettyNameQ()); + } + nodep->addOptionsp(new AstCoverOption{optp->fileline(), optType, + optp->valuep()->cloneTree(false)}); + VL_DO_DANGLING(optp->deleteTree(), optp); + } + } + iterateChildren(nodep); + } + + void visit(AstCoverCross* nodep) override { + cleanFileline(nodep); + // Distribute the parse-time raw cross_body list (rawBodyp, op3) into the + // typed optionsp slot. Nodes are properly in-tree here so + // unlinkFrBack() works cleanly with no bison-list hackery. + for (AstNode *itemp = nodep->rawBodyp(), *nextp; itemp; itemp = nextp) { + nextp = itemp->nextp(); + itemp->unlinkFrBack(); + if (AstCoverOption* const optp = VN_CAST(itemp, CoverOption)) { + nodep->addOptionsp(optp); + } else { + // AstCgOptionAssign, AstFunc, and other unsupported items + VL_DO_DANGLING(itemp->deleteTree(), itemp); + } + } + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { // Default: Just iterate cleanFileline(nodep); diff --git a/src/V3MergeCond.cpp b/src/V3MergeCond.cpp index 6cddb01e7..7cadd2487 100644 --- a/src/V3MergeCond.cpp +++ b/src/V3MergeCond.cpp @@ -296,6 +296,7 @@ class CodeMotionAnalysisVisitor final : public VNVisitorConst { } // 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/V3ParseGrammar.h b/src/V3ParseGrammar.h index c7b251f5a..7ec327170 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: @@ -94,97 +96,6 @@ 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); - - // 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() - { - AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr}; - funcp->addStmtsp(sampleArgs); - 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); - } - } AstDisplay* createDisplayError(FileLine* fileline) { AstDisplay* nodep = new AstDisplay{fileline, VDisplayType::DT_ERROR, "", nullptr, nullptr}; AstNode::addNext(nodep, new AstStop{fileline, false}); diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index ac48b4032..0fc4f2860 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -343,11 +343,15 @@ class TimingSuspendableVisitor final : public VNVisitor { } } void visit(AstNodeCCall* nodep) override { - new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()), getSuspendDepVtx(m_procp), - P_CALL}; + AstCFunc* const funcp = nodep->funcp(); + UASSERT_OBJ(funcp, nodep, "AstNodeCCall must have non-null funcp post-link"); + UASSERT_OBJ(m_procp, nodep, "AstNodeCCall must be inside a procedure/CFunc/Begin"); - new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()), - getNeedsProcDepVtx(m_procp), P_CALL}; + 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); } @@ -961,8 +965,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 35f2e2b6c..4c267a555 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -224,6 +224,7 @@ class WidthVisitor final : public VNVisitor { const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations const AstEnumItem* m_enumItemp = nullptr; // Current enum item AstNodeFTask* m_ftaskp = nullptr; // Current function/task + AstClass* m_cgClassp = nullptr; // Current covergroup class AstNodeModule* m_modep = nullptr; // Current module const AstConstraint* m_constraintp = nullptr; // Current constraint AstNodeProcedure* m_procedurep = nullptr; // Current final/always @@ -1744,8 +1745,20 @@ 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. + // m_cgClassp is always set here: AstCgOptionAssign only appears in covergroup + // class bodies, and visitClass sets m_cgClassp before iterating children. + if (nodep->name() == "auto_bin_max" && !nodep->typeOption()) { + // Extract constant value + if (AstConst* constp = VN_CAST(nodep->valuep(), Const)) { + m_cgClassp->cgAutoBinMax(constp->toSInt()); + UINFO(6, " Covergroup " << m_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 { @@ -3387,7 +3400,16 @@ class WidthVisitor final : public VNVisitor { return AstEqWild::newTyped(itemp->fileline(), exprp, itemp->unlinkFrBack()); } void visit(AstInsideRange* nodep) override { - // Just do each side; AstInside will rip these nodes out later + // Just do each side; AstInside will rip these nodes out later. + // When m_vup is null, this range appears outside a normal expression context (e.g. + // in a covergroup bin declaration). Pre-fold constant arithmetic in that case + // (e.g., AstNegate(Const) -> Const) so children have their types set before widthing. + // We cannot do this unconditionally: in a normal 'inside' expression (m_vup set), + // range bounds may be enum refs not yet widthed, and constifyEdit would crash. + if (!m_vup) { + V3Const::constifyEdit(nodep->lhsp()); // lhsp may change + V3Const::constifyEdit(nodep->rhsp()); // rhsp may change + } userIterateAndNext(nodep->lhsp(), m_vup); userIterateAndNext(nodep->rhsp(), m_vup); nodep->dtypeFrom(nodep->lhsp()); @@ -7460,6 +7482,8 @@ class WidthVisitor final : public VNVisitor { // Must do extends first, as we may in functions under this class // start following a tree of extends that takes us to other classes userIterateAndNext(nodep->extendsp(), nullptr); + VL_RESTORER(m_cgClassp); + if (nodep->isCovergroup()) m_cgClassp = nodep; userIterateChildren(nodep, nullptr); // First size all members } void visit(AstNodeModule* nodep) override { diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f0b4d714b..26eabfb9f 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" @@ -236,6 +237,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()); diff --git a/src/VlcMain.cpp b/src/VlcMain.cpp index e5113a966..40172d75b 100644 --- a/src/VlcMain.cpp +++ b/src/VlcMain.cpp @@ -66,6 +66,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) { DECL_OPTION("-annotate-all", OnOff, &m_annotateAll); DECL_OPTION("-annotate-min", Set, &m_annotateMin); DECL_OPTION("-annotate-points", OnOff, &m_annotatePoints); + DECL_OPTION("-covergroup", OnOff, &m_covergroup); DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); }); DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); }); DECL_OPTION("-filter-type", Set, &m_filterType); @@ -143,6 +144,8 @@ int main(int argc, char** argv) { V3Error::abortIfWarnings(); if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut()); + if (top.opt.covergroup()) top.covergroup(); + if (top.opt.rank()) { top.rank(); top.tests().dump(false); diff --git a/src/VlcOptions.h b/src/VlcOptions.h index 7d957f722..9cd91f0a2 100644 --- a/src/VlcOptions.h +++ b/src/VlcOptions.h @@ -39,6 +39,7 @@ class VlcOptions final { bool m_annotateAll = false; // main switch: --annotate-all int m_annotateMin = 10; // main switch: --annotate-min I bool m_annotatePoints = false; // main switch: --annotate-points + bool m_covergroup = false; // main switch: --covergroup string m_filterType = "*"; // main switch: --filter-type VlStringSet m_readFiles; // main switch: --read bool m_rank = false; // main switch: --rank @@ -67,6 +68,7 @@ public: int annotateMin() const { return m_annotateMin; } bool countOk(uint64_t count) const { return count >= static_cast(m_annotateMin); } bool annotatePoints() const { return m_annotatePoints; } + bool covergroup() const { return m_covergroup; } bool rank() const { return m_rank; } bool unlink() const { return m_unlink; } string writeFile() const { return m_writeFile; } diff --git a/src/VlcPoint.h b/src/VlcPoint.h index 372b9f2bf..3734b3c04 100644 --- a/src/VlcPoint.h +++ b/src/VlcPoint.h @@ -65,6 +65,11 @@ public: string comment() const { return keyExtract(VL_CIK_COMMENT, m_name.c_str()); } string hier() const { return keyExtract(VL_CIK_HIER, m_name.c_str()); } string type() const { return typeExtract(m_name.c_str()); } + // Covergroup-specific key accessors (long keys, no short-key alias) + string page() const { return keyExtract("page", m_name.c_str()); } + string bin() const { return keyExtract("bin", m_name.c_str()); } + string binType() const { return keyExtract("bin_type", m_name.c_str()); } + bool isCross() const { return !keyExtract("cross", m_name.c_str()).empty(); } string thresh() const { // string as maybe "" return keyExtract(VL_CIK_THRESH, m_name.c_str()); diff --git a/src/VlcTop.cpp b/src/VlcTop.cpp index aad1f7f0d..9f8bed6ca 100644 --- a/src/VlcTop.cpp +++ b/src/VlcTop.cpp @@ -25,6 +25,9 @@ #include #include +#include +#include +#include #include #include @@ -205,6 +208,171 @@ void VlcTop::rank() { } } +void VlcTop::covergroup() { + UINFO(2, "covergroup..."); + // Structs for accumulating report data + struct BinEntry final { + std::string name; + std::string binType; // "ignore", "illegal", or "" (normal) + bool covered = false; + uint64_t count = 0; + }; + struct CpEntry final { + std::string name; + bool isCross = false; + std::vector bins; + uint64_t normalTotal = 0; + uint64_t normalCovered = 0; + }; + struct CgEntry final { + std::string typeName; + std::string filename; + int lineno = 0; + std::vector coverpoints; + std::map cpIndex; + }; + + std::map cgMap; + + // Collect covergroup points from all loaded coverage data + for (const auto& nameNum : m_points) { + const VlcPoint& pt = m_points.pointNumber(nameNum.second); + if (pt.type() != "covergroup") continue; + + const std::string page = pt.page(); + // Page format: "v_covergroup/" + const std::string pagePrefix = "v_covergroup/"; + if (page.size() <= pagePrefix.size()) continue; + const std::string cgTypeName = page.substr(pagePrefix.size()); + + // Parse hier: ".." + const std::string hier = pt.hier(); + const size_t dot1 = hier.find('.'); + if (dot1 == std::string::npos) continue; + const size_t dot2 = hier.find('.', dot1 + 1); + if (dot2 == std::string::npos) continue; + const std::string cpName = hier.substr(dot1 + 1, dot2 - dot1 - 1); + const std::string binName = hier.substr(dot2 + 1); + + auto& cg = cgMap[cgTypeName]; + if (cg.typeName.empty()) { + cg.typeName = cgTypeName; + cg.filename = pt.filename(); + cg.lineno = pt.lineno(); + } + + auto it = cg.cpIndex.find(cpName); + size_t cpIdx; + if (it == cg.cpIndex.end()) { + cpIdx = cg.coverpoints.size(); + cg.cpIndex[cpName] = cpIdx; + CpEntry cp; + cp.name = cpName; + cp.isCross = pt.isCross(); + cg.coverpoints.push_back(cp); + } else { + cpIdx = it->second; + } + + BinEntry bin; + bin.name = binName; + bin.binType = pt.binType(); + // Threshold: use per-bin thresh key (option.at_least) if present, else 1 (SV default) + const std::string threshStr = pt.thresh(); + const uint64_t binThresh = threshStr.empty() ? 1 : std::stoull(threshStr); + bin.count = pt.count(); + bin.covered = (bin.count >= binThresh); + cg.coverpoints[cpIdx].bins.push_back(bin); + + if (bin.binType.empty()) { + ++cg.coverpoints[cpIdx].normalTotal; + if (bin.covered) ++cg.coverpoints[cpIdx].normalCovered; + } + } + + // Compute grand totals + uint64_t grandTotal = 0, grandCovered = 0, grandIgnored = 0, grandIllegal = 0; + for (const auto& cgPair : cgMap) { + for (const auto& cp : cgPair.second.coverpoints) { + grandTotal += cp.normalTotal; + grandCovered += cp.normalCovered; + for (const auto& bin : cp.bins) { + if (bin.binType == "ignore") + ++grandIgnored; + else if (bin.binType == "illegal") + ++grandIllegal; + } + } + } + + // Format a percentage string "xx.xx" + const auto pctStr = [](uint64_t covered, uint64_t total) -> std::string { + std::ostringstream oss; + const double pct = (total == 0) ? 100.0 : (100.0 * covered / total); + oss << std::fixed << std::setprecision(2) << pct; + return oss.str(); + }; + + const std::string divider(78, '-'); + + // Header and grand total + std::cout << "COVERGROUP COVERAGE REPORT\n"; + std::cout << "==========================\n"; + std::cout << "\n"; + std::cout << "TOTAL: " << grandCovered << "/" << grandTotal << " bins covered (" + << pctStr(grandCovered, grandTotal) << "%)\n"; + if (grandIgnored || grandIllegal) + std::cout << " (" << grandIgnored << " ignored, " << grandIllegal << " illegal)\n"; + + // One section per covergroup type (map is sorted alphabetically) + for (const auto& cgPair : cgMap) { + const CgEntry& cg = cgPair.second; + + uint64_t cgTotal = 0, cgCovered = 0; + for (const auto& cp : cg.coverpoints) { + cgTotal += cp.normalTotal; + cgCovered += cp.normalCovered; + } + + std::cout << "\n" << divider << "\n"; + std::cout << "Covergroup Type: " << cg.typeName << " [" << cg.filename << ":" << cg.lineno + << "]\n"; + std::cout << " Type Coverage: " << cgCovered << "/" << cgTotal << " bins (" + << pctStr(cgCovered, cgTotal) << "%)\n"; + + for (const auto& cp : cg.coverpoints) { + std::cout << "\n"; + std::cout << " " << (cp.isCross ? "Cross" : "Coverpoint") << ": " << cp.name << "\n"; + std::cout << " Coverage: " << cp.normalCovered << "/" << cp.normalTotal << " bins (" + << pctStr(cp.normalCovered, cp.normalTotal) << "%)\n"; + std::cout << " Bins:\n"; + + // Align bin name column to max name length in this coverpoint + size_t maxNameLen = 0; + for (const auto& bin : cp.bins) + if (bin.name.size() > maxNameLen) maxNameLen = bin.name.size(); + + for (const auto& bin : cp.bins) { + const char* status; + if (bin.binType == "ignore") + status = "IGNORE "; + else if (bin.binType == "illegal") + status = "ILLEGAL"; + else if (bin.covered) + status = "COVERED"; + else + status = "ZERO "; + + std::cout << " " << status << " " << std::left + << std::setw(static_cast(maxNameLen)) << bin.name << std::right + << " " << bin.count << " hits\n"; + } + } + } + + std::cout << "\n" << divider << "\n"; +} + //###################################################################### void VlcTop::annotateCalc() { diff --git a/src/VlcTop.h b/src/VlcTop.h index 13e0d93d6..06007fdd3 100644 --- a/src/VlcTop.h +++ b/src/VlcTop.h @@ -55,6 +55,7 @@ public: // METHODS void annotate(const string& dirname); + void covergroup(); void readCoverage(const string& filename, bool nonfatal = false); void writeCoverage(const string& filename); void writeInfo(const string& filename); diff --git a/src/verilog.y b/src/verilog.y index 6b6a453e6..536dd07c5 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3958,16 +3958,16 @@ value_range: // ==IEEE: value_range/open_value_range covergroup_value_range: // ==IEEE-2012: covergroup_value_range cgexpr { $$ = $1; } | '[' cgexpr ':' cgexpr ']' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: covergroup value range"); DEL($2, $4); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: covergroup value range '[...]'"); DEL($2, $4); } // // IEEE-2023: added all four: // // Skipped as '$' is part of our expr // // IEEE-2023: '[' '$' ':' cgexpr ']' // // Skipped as '$' is part of our expr // // IEEE-2023: '[' cgexpr ':' '$' ']' | '[' cgexpr yP_PLUSSLASHMINUS cgexpr ']' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: covergroup value range"); DEL($2, $4); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: covergroup value range '[...]'"); DEL($2, $4); } | '[' cgexpr yP_PLUSPCTMINUS cgexpr ']' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: covergroup value range"); DEL($2, $4); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: covergroup value range '[...]'"); DEL($2, $4); } ; caseCondList: // IEEE: part of case_item @@ -6933,40 +6933,27 @@ 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}; + // Every covergroup has option/type_option members (added by V3LinkParse) + // referencing std:: types, so mark std as needed at parse time. + v3Global.setUsesStdPackage(); + 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"); - } + { $$ = nullptr; + BBUNSUP($1, "Unsupported: covergroup inheritance (extends)"); + DEL($5); } ; cgPortListE: @@ -7013,29 +7000,33 @@ 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); } + { $$ = new AstCoverpoint{$1, "", $2, $3, $4}; } // // 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);} + { $$ = new AstCoverpoint{$3, *$1, $4, $5, $6}; } // // 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);} + { $$ = new AstCoverpoint{$4, *$2, $5, $6, $7}; + 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); } + { $$ = new AstCoverpoint{$5, *$3, $6, $7, $8}; + 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); } + { $$ = new AstCoverpoint{$5, *$3, $6, $7, $8}; + 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); } + { $$ = new AstCoverpoint{$5, *$3, $6, $7, $8}; + DEL($2); } | signing id/*cover_point_id*/ ':' yCOVERPOINT expr iffE bins_or_empty - { $$ = nullptr; BBCOVERIGN($4, "Ignoring unsupported: coverpoint"); DEL($5, $6, $7); } + { $$ = new AstCoverpoint{$4, *$2, $5, $6, $7}; } // // IEEE-2012: | bins_or_empty { $$ = $1; } ; -iffE: // IEEE: part of cover_point, others +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 @@ -7059,39 +7050,104 @@ 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 + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, false}; + if ($3) binp->isArray(true); + $$ = binp; 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, "Unsupported: 'bins' array (non-auto)"); + DEL($4, $6); + } + } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, true, false}; + if ($3) binp->isArray(true); + $$ = binp; DEL($8); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, true}; + if ($3) binp->isArray(true); + $$ = binp; DEL($8); } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, false}; + BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); + DEL($10, $12); $$ = binp; } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, true, false}; + BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); + DEL($10, $12); $$ = binp; } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, true}; + BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); + DEL($10, $12); $$ = binp; } + | yBINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); 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, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } + | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } + | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE + { $$ = nullptr; BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); 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), VCoverBinsType{VCoverBinsType::BINS_TRANSITION}, isArray != nullptr}; + DEL($6); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { FileLine* isArray = $3; + $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_IGNORE}, isArray != nullptr}; + DEL($6); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { FileLine* isArray = $3; + $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_ILLEGAL}, isArray != nullptr}; + DEL($6); } + | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} + | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} + | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); 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::BINS_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, "Unsupported: 'sequence' in default cover bin"); DEL($7); } + | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE + { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } + | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE + { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); 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*/ } - | '[' cgexpr ']' { $$ = nullptr; /*UNSUP*/ DEL($2); } - ; - -bins_keyword: // ==IEEE: bins_keyword - yBINS { $$ = $1; /*UNSUP*/ } - | yILLEGAL_BINS { $$ = $1; /*UNSUP*/ } - | yIGNORE_BINS { $$ = $1; /*UNSUP*/ } + | '[' ']' { $$ = $1; /* Mark as array */ } + | '[' cgexpr ']' { BBCOVERIGN($1, "Unsupported: 'bins' explicit array size (treated as '[]')"); DEL($2); $$ = $1; } ; trans_list: // ==IEEE: trans_list @@ -7099,30 +7155,40 @@ trans_list: // ==IEEE: trans_list | 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 { + AstCoverTransItem* const itemp = static_cast($1); + $$ = new AstCoverTransSet{$1, itemp}; + } | trans_set yP_EQGT trans_range_list - { $$ = $1; BBCOVERIGN($2, "Ignoring unsupported: cover trans set '=>'"); DEL($3); } + { + AstCoverTransSet* const setp = static_cast($1); + AstCoverTransItem* const itemp = static_cast($3); + setp->addItemsp(itemp); + $$ = setp; + } ; -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); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[*]' in cover transition"); DEL($1, $3); } | trans_item yP_BRASTAR cgexpr ':' cgexpr ']' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[*'"); DEL($1, $3, $5); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[*]' in cover transition"); DEL($1, $3, $5); } | trans_item yP_BRAMINUSGT cgexpr ']' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[->'"); DEL($1, $3); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[->' in cover transition"); DEL($1, $3); } | trans_item yP_BRAMINUSGT cgexpr ':' cgexpr ']' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[->'"); DEL($1, $3, $5); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[->' in cover transition"); DEL($1, $3, $5); } | trans_item yP_BRAEQ cgexpr ']' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[='"); DEL($1, $3); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[=]' in cover transition"); DEL($1, $3); } | trans_item yP_BRAEQ cgexpr ':' cgexpr ']' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover '[='"); DEL($1, $3, $5); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '[=]' in cover transition"); DEL($1, $3, $5); } ; -trans_item: // ==IEEE: range_list +trans_item: // ==IEEE: range_list (returns range list node) covergroup_range_list { $$ = $1; } ; @@ -7134,9 +7200,28 @@ 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) nodep->addRawBodyp($6); + if ($5) { + $5->v3warn(COVERIGN, "Unsupported: 'iff' in coverage cross"); + 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) nodep->addRawBodyp($4); + if ($3) { + $3->v3warn(COVERIGN, "Unsupported: 'iff' in coverage cross"); + VL_DO_DANGLING($3->deleteTree(), $3); + } + $$ = nodep; + } ; list_of_cross_items: // ==IEEE: list_of_cross_items @@ -7151,7 +7236,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 @@ -7171,12 +7257,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(), "Unsupported: 'function' in coverage cross body"); 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, "Unsupported: explicit coverage cross bins"); DEL($4, $5); } + | yIGNORE_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); } + | yILLEGAL_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); } | error ';' { $$ = nullptr; } // LCOV_EXCL_LINE ; @@ -7184,28 +7274,28 @@ select_expression: // ==IEEE: select_expression select_expression_r { $$ = $1; } | select_expression yP_ANDAND select_expression - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: coverage select expression '&&'"); DEL($1, $3); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '&&' in coverage select expression"); DEL($1, $3); } | select_expression yP_OROR select_expression - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: coverage select expression '||'"); DEL($1, $3); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '||' in coverage select expression"); DEL($1, $3); } ; // This non-terminal exists to disambiguate select_expression and make "with" bind tighter select_expression_r: // // IEEE: select_condition expanded here yBINSOF '(' bins_expression ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select expression 'binsof'"); DEL($3); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'binsof' in coverage select expression"); DEL($3); } | '!' yBINSOF '(' bins_expression ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select expression 'binsof'"); DEL($4); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'binsof' in coverage select expression"); DEL($4); } | yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverage select expression 'intersect'"); DEL($3, $7); } + { $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($7); } | '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' { } - { $$ = nullptr; BBCOVERIGN($5, "Ignoring unsupported: coverage select expression 'intersect'"); DEL($4, $8); } + { $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($4, $8); } | yWITH__PAREN '(' cgexpr ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select expression with"); DEL($3); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'with' in coverage select expression"); DEL($3); } | '!' yWITH__PAREN '(' cgexpr ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select expression with"); DEL($4); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'with' in coverage select expression"); DEL($4); } | select_expression_r yWITH__PAREN '(' cgexpr ')' - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: coverage select expression with"); DEL($1, $4); } + { $$ = nullptr; BBCOVERIGN($2, "Unsupported: 'with' in coverage select expression"); DEL($1, $4); } // // IEEE-2012: Need clarification as to precedence //UNSUP yWITH__PAREN '(' cgexpr ')' yMATCHES cgexpr { } // // IEEE-2012: Need clarification as to precedence @@ -7223,7 +7313,7 @@ select_expression_r: //UNSUP cgexpr yMATCHES cgexpr {..} //UNSUP // Below are all removed | idAny '(' list_of_argumentsE ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage select function call"); DEL($3); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: function call in coverage select expression"); DEL($3); } //UNSUP // Above are all removed, replace with: ; @@ -7242,7 +7332,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'"); @@ -7253,7 +7343,7 @@ coverage_eventE: // IEEE: [ coverage_event ] } } | yP_ATAT '(' block_event_expression ')' - { $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: coverage '@@' events"); DEL($3); } + { $$ = nullptr; BBCOVERIGN($1, "Unsupported: '@@' coverage event"); DEL($3); } ; block_event_expression: // ==IEEE: block_event_expression @@ -7751,11 +7841,15 @@ class_item: // ==IEEE: class_item | timeunits_declaration { $$ = $1; } | covergroup_declaration { - const string cgName = $1->name(); - $1->name("__vlAnonCG_" + cgName); - AstVar* const newp = new AstVar{$1->fileline(), VVarType::VAR, cgName, - VFlagChildDType{}, new AstRefDType($1->fileline(), $1->name())}; - $$ = addNextNull($1, newp); + if ($1) { + const string cgName = $1->name(); + $1->name("__vlAnonCG_" + cgName); + AstVar* const newp = new AstVar{$1->fileline(), VVarType::VAR, cgName, + VFlagChildDType{}, new AstRefDType($1->fileline(), $1->name())}; + $$ = addNextNull($1, newp); + } else { + $$ = nullptr; + } } // // local_parameter_declaration under parameter_declaration | parameter_declaration ';' { $$ = $1; } diff --git a/test_regress/driver.py b/test_regress/driver.py index 44156b8ab..731e7272e 100755 --- a/test_regress/driver.py +++ b/test_regress/driver.py @@ -2802,6 +2802,41 @@ class VlTest: self._file_contents_cache[filename] = str(fh.read()) return self._file_contents_cache[filename] + def covergroup_coverage_report(self, outfile: str = None) -> str: + """Parse coverage.dat and write a sorted covergroup bin hit-count report. + + Lines have the form: [ [bin_type]]: + ignore_bins and illegal_bins are annotated with [ignore] / [illegal]. + + Returns the path to the written report file. + """ + if outfile is None: + outfile = self.obj_dir + "/covergroup_report.txt" + contents = self.file_contents(self.coverage_filename) + entries = [] + for m in re.finditer(r"C '([^']+)' (\d+)", contents): + entry, count = m.group(1), m.group(2) + if '\x01t\x02covergroup' not in entry: + continue + h_m = re.search(r'\x01h\x02([^\x01]+)', entry) + if not h_m: + continue + hier = h_m.group(1) + bt_m = re.search(r'\x01bin_type\x02([^\x01]+)', entry) + cross_m = re.search(r'\x01cross\x021', entry) + annotations = [] + if bt_m: + annotations.append(bt_m.group(1)) + if cross_m: + annotations.append("cross") + label = f"{hier} [{','.join(annotations)}]" if annotations else hier + entries.append((hier, label, int(count))) + entries.sort() + with open(outfile, 'w', encoding='utf-8') as fh: + for _hier, label, count in entries: + fh.write(f"{label}: {count}\n") + return outfile + @staticmethod def _file_contents_static(filename: str) -> str: if filename not in VlTest._file_contents_cache: diff --git a/test_regress/t/t_covergroup_args.v b/test_regress/t/t_covergroup_args.v index ca8957a45..eb3ee3b34 100644 --- a/test_regress/t/t_covergroup_args.v +++ b/test_regress/t/t_covergroup_args.v @@ -4,17 +4,23 @@ // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 +// A plain (non-covergroup) class - exercises the non-covergroup class scope/varscope paths +class PlainClass; + int x; +endclass + // verilator lint_off COVERIGN module t; + int i, j; + covergroup cg(int var1, int var2 = 42); + cp1: coverpoint i; // Non-empty body with args: exercises constructor-body path endgroup cg cov1 = new(69, 77); cg cov2 = new(69); - - int i, j; - real r; + PlainClass plain_inst = new; // Non-covergroup class instance: exercises early-return paths function void x(); cov1.set_inst_name("the_inst_name"); @@ -23,16 +29,14 @@ module t; cov1.stop(); void'(cov2.get_coverage()); - r = cov2.get_coverage(); - r = cov2.get_coverage(i, j); + void'(cov2.get_coverage(i, j)); // verilator lint_off IGNOREDRETURN cov2.get_inst_coverage(); // verilator lint_on IGNOREDRETURN - r = cov2.get_inst_coverage(i, j); + void'(cov2.get_inst_coverage(i, j)); - cg::get_coverage(); - r = cg::get_coverage(); - r = cg::get_coverage(i, j); + void'(cg::get_coverage()); + void'(cg::get_coverage(i, j)); endfunction endmodule diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out new file mode 100644 index 000000000..4c0767563 --- /dev/null +++ b/test_regress/t/t_covergroup_array_bins.out @@ -0,0 +1,4 @@ +cg.data.grouped: 2 +cg.data.values: 3 +cg2.cp.range_arr: 3 +cg3.cp.range_sized: 3 diff --git a/test_regress/t/t_covergroup_array_bins.py b/test_regress/t/t_covergroup_array_bins.py new file mode 100755 index 000000000..73e47da7a --- /dev/null +++ b/test_regress/t/t_covergroup_array_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage --Wno-COVERIGN']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() 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..b6e6d44f1 --- /dev/null +++ b/test_regress/t/t_covergroup_array_bins.v @@ -0,0 +1,78 @@ +// 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, including InsideRange and AstRange + +module t; + 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 + + // cg2: exercises InsideRange in array bins (e.g., bins r[] = {[0:3]}) + covergroup cg2; + cp: coverpoint data { + bins range_arr[] = {[0:3]}; // InsideRange -> 4 separate bins + } + endgroup + + // cg3: exercises AstRange in array bins (e.g., bins r[N] = {[lo:hi]}) + covergroup cg3; + cp: coverpoint data { + bins range_sized[4] = {[4:7]}; // AstRange with explicit count + } + endgroup + + initial begin + cg cg_inst; + cg2 cg2_inst; + cg3 cg3_inst; + + cg_inst = new(); + cg2_inst = new(); + cg3_inst = new(); + + // Hit first array bin value (1) + data = 1; + cg_inst.sample(); + + // Hit second array bin value (5) + data = 5; + cg_inst.sample(); + + // Hit the grouped bin (covers all of 2, 6, 10) + data = 6; + cg_inst.sample(); + + // Hit third array bin value (9) + data = 9; + cg_inst.sample(); + + // Verify hitting other values in grouped bin doesn't increase coverage + data = 2; + cg_inst.sample(); + + // Hit range_arr bins (InsideRange [0:3]) + data = 0; cg2_inst.sample(); + data = 1; cg2_inst.sample(); + data = 2; cg2_inst.sample(); + + // Hit range_sized bins (AstRange [4:7]) + data = 4; cg3_inst.sample(); + data = 5; cg3_inst.sample(); + data = 6; cg3_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_auto_bin_max.out b/test_regress/t/t_covergroup_auto_bin_max.out new file mode 100644 index 000000000..4f4308cc9 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bin_max.out @@ -0,0 +1,19 @@ +cg1.cp_data3.auto_0: 1 +cg1.cp_data3.auto_1: 0 +cg1.cp_data3.auto_2: 0 +cg1.cp_data3.auto_3: 1 +cg1.cp_data3.auto_4: 0 +cg1.cp_data3.auto_5: 0 +cg1.cp_data3.auto_6: 0 +cg1.cp_data3.auto_7: 0 +cg2.cp_data3.auto_0: 1 +cg2.cp_data3.auto_1: 0 +cg2.cp_data3.auto_2: 1 +cg2.cp_data3.auto_3: 0 +cg3.cp_data3.auto_0: 1 +cg3.cp_data3.auto_1: 1 +cg4.cp.auto_0: 0 +cg4.cp.auto_1: 1 +cg4.cp.auto_2: 1 +cg4.cp.auto_3: 1 +cg4.cp.ign [ignore]: 0 diff --git a/test_regress/t/t_covergroup_auto_bin_max.py b/test_regress/t/t_covergroup_auto_bin_max.py new file mode 100755 index 000000000..280c22c29 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bin_max.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage', '-Wno-UNSIGNED', '-Wno-CMPCONST']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_auto_bin_max.v b/test_regress/t/t_covergroup_auto_bin_max.v new file mode 100644 index 000000000..9cecc34cc --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bin_max.v @@ -0,0 +1,73 @@ +// 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-FileCopyrightText: 2024 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test implicit auto-bin creation (no explicit bins) and option.auto_bin_max + +module t; + + logic [2:0] data3; + logic [3:0] data4; // 4-bit signal for range-bin path tests + + // Test 1: auto_bin_max default (64) - creates 8 bins for 3-bit signal + covergroup cg1; + cp_data3: coverpoint data3; + endgroup + + // Test 2: auto_bin_max = 4 at covergroup level - creates 4 bins: [0:1],[2:3],[4:5],[6:7] + covergroup cg2; + option.auto_bin_max = 4; + cp_data3: coverpoint data3; + endgroup + + // Test 3: auto_bin_max and at_least at *coverpoint* level (lines 207, 209) + covergroup cg3; + cp_data3: coverpoint data3 { + option.auto_bin_max = 2; // coverpoint-level: creates 2 bins [0:3],[4:7] + option.at_least = 3; // coverpoint-level at_least + } + endgroup + + // Test 4: range-bin skip path (lines 287, 356-359). + // auto_bin_max=4 on 4-bit signal -> 4 range bins: [0:3],[4:7],[8:11],[12:15]. + // ignore_bins {[0:3]} excludes all values in the first range -> that bin is skipped. + covergroup cg4; + option.auto_bin_max = 4; + cp: coverpoint data4 { + ignore_bins ign = {[0:3]}; // first range excluded from coverage + } + endgroup + + initial begin + cg1 cg1_inst; + cg2 cg2_inst; + cg3 cg3_inst; + cg4 cg4_inst; + + cg1_inst = new; + cg2_inst = new; + cg3_inst = new; + cg4_inst = new; + + data3 = 0; cg1_inst.sample(); + data3 = 3; cg1_inst.sample(); + + data3 = 0; cg2_inst.sample(); + data3 = 4; cg2_inst.sample(); + + data3 = 1; cg3_inst.sample(); + data3 = 5; cg3_inst.sample(); + + // Sample valid (non-ignored) values for cg4 + data4 = 4; cg4_inst.sample(); // [4:7] bin + data4 = 8; cg4_inst.sample(); // [8:11] bin + data4 = 12; cg4_inst.sample(); // [12:15] bin + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_auto_bins.out b/test_regress/t/t_covergroup_auto_bins.out new file mode 100644 index 000000000..41d820337 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bins.out @@ -0,0 +1,15 @@ +cg.data.auto[0]: 1 +cg.data.auto[1]: 1 +cg.data.auto[2]: 1 +cg.data.auto[3]: 1 +cg2.data64.auto_0: 1 +cg2.data64.auto_1: 1 +cg_4bit.data4.auto[0]: 1 +cg_4bit.data4.auto[1]: 1 +cg_4bit.data4.auto[2]: 1 +cg_4bit.data4.auto[3]: 1 +cg_4bit_excl.data4.auto[0]: 1 +cg_4bit_excl.data4.auto[1]: 0 +cg_4bit_excl.data4.auto[2]: 1 +cg_4bit_excl.data4.auto[3]: 0 +cg_4bit_excl.data4.bad [ignore]: 0 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..ceec4c59e --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..62027dd13 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_bins.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test automatic bins: bins auto[N] +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + /* verilator lint_off CMPCONST */ + logic [2:0] data; // 3-bit: 0-7 + logic [3:0] data4; // 4-bit: exercises width<64 path in maxVal computation + logic [63:0] data64; // 64-bit: exercises width>=64 (UINT64_MAX) path + + covergroup cg; + coverpoint data { + bins auto[4]; // Should create 4 bins: [0:1], [2:3], [4:5], [6:7] + } + endgroup + + // 4-bit auto bins: exercises (width < 64) path: maxVal = (1<<4)-1 = 15 + covergroup cg_4bit; + coverpoint data4 { + bins auto[4]; // Creates 4 bins: [0:3], [4:7], [8:11], [12:15] + } + endgroup + + // 4-bit auto bins with ignore_bins: exercises excluded-value skip path + covergroup cg_4bit_excl; + coverpoint data4 { + ignore_bins bad = {0}; // value 0 excluded from auto expansion + bins auto[4]; + } + endgroup + + // auto_bin_max=2 on 64-bit: exercises numTotalValues=UINT64_MAX path + covergroup cg2; + option.auto_bin_max = 2; + coverpoint data64; + endgroup + /* verilator lint_on CMPCONST */ + + initial begin + automatic cg cg_inst = new; + automatic cg_4bit cg4_inst = new; + automatic cg_4bit_excl cg4e_inst = new; + automatic cg2 cg2_inst = new; + + // Sample 3-bit cg: one value per bin + data = 0; cg_inst.sample(); + data = 2; cg_inst.sample(); + data = 5; cg_inst.sample(); + data = 7; cg_inst.sample(); + + // Sample 4-bit bins + data4 = 0; cg4_inst.sample(); // bin [0:3] + data4 = 7; cg4_inst.sample(); // bin [4:7] + data4 = 10; cg4_inst.sample(); // bin [8:11] + data4 = 14; cg4_inst.sample(); // bin [12:15] + + // Sample 4-bit with exclusion (value 0 excluded; bins start at 1) + data4 = 1; cg4e_inst.sample(); + data4 = 8; cg4e_inst.sample(); + + // Sample both 64-bit range bins + data64 = 64'd0; cg2_inst.sample(); // auto[0] + data64 = 64'hFFFF_FFFF_FFFF_FFFF; cg2_inst.sample(); // auto[1] + + $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..771eaa90c --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample.cpp @@ -0,0 +1,29 @@ +// -*- 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-FileCopyrightText: 2026 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_timing.py b/test_regress/t/t_covergroup_auto_sample_timing.py new file mode 100755 index 000000000..67cc5ed67 --- /dev/null +++ b/test_regress/t/t_covergroup_auto_sample_timing.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Use the same .v file as the non-timing test +test.top_filename = "t/t_covergroup_clocked_sample.v" + +test.compile(v_flags2=["--timing"]) + +test.execute() + +test.passes() 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..c9926061a --- /dev/null +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -0,0 +1,30 @@ +%Error: t/t_covergroup_autobins_bad.v:17:12: 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:12: Automatic bins array size must be >= 1, got 0 + : ... note: In instance 't' + 24 | bins auto[0]; + | ^~~~ +%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin value list; values must be constants + : ... note: In instance 't' + 34 | ignore_bins ign = {size_var}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:31:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 31 | bins b[] = {[size_var:size_var]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:32:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 32 | bins b_mixed[] = {[0:size_var]}; + | ^~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:33:18: Non-constant expression in bin range; values must be constants + : ... note: In instance 't' + 33 | bins b2 = {size_var}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin range; values must be constants + : ... note: In instance 't' + 34 | ignore_bins ign = {size_var}; + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_option_bad.py b/test_regress/t/t_covergroup_autobins_bad.py similarity index 81% rename from test_regress/t/t_covergroup_option_bad.py rename to test_regress/t/t_covergroup_autobins_bad.py index 4d0f745d0..7206b3e2f 100755 --- a/test_regress/t/t_covergroup_option_bad.py +++ b/test_regress/t/t_covergroup_autobins_bad.py @@ -4,7 +4,7 @@ # 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-FileCopyrightText: 2025 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('vlt') test.lint(expect_filename=test.golden_filename, - verilator_flags2=['--assert --error-limit 1000'], + 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..b0e38f429 --- /dev/null +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -0,0 +1,43 @@ +// 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 (zero) + covergroup cg2; + cp1: coverpoint cp_expr { + bins auto[0]; + } + endgroup + + // Error: non-constant value in bin ranges + covergroup cg3; + cp1: coverpoint cp_expr { + bins b[] = {[size_var:size_var]}; // non-constant array bins range (both bounds non-const) + bins b_mixed[] = {[0:size_var]}; // non-constant array bins range (max bound non-const) + bins b2 = {size_var}; // non-constant simple bin value + ignore_bins ign = {size_var}; // non-constant ignore_bins value + } + 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.out b/test_regress/t/t_covergroup_bin_counts.out new file mode 100644 index 000000000..c06c1014b --- /dev/null +++ b/test_regress/t/t_covergroup_bin_counts.out @@ -0,0 +1,2 @@ +cg.data.low: 3 +cg.data.zero: 1 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_bin_counts.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..ef2a50598 --- /dev/null +++ b/test_regress/t/t_covergroup_bin_counts.v @@ -0,0 +1,33 @@ +// 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*/); + logic [3:0] data; + + covergroup cg; + coverpoint data { + bins zero = {0}; + bins low = {[1:3]}; + } + endgroup + + cg cg_inst; + + initial begin + cg_inst = new; + + 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 + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_clocked_sample.out b/test_regress/t/t_covergroup_clocked_sample.out new file mode 100644 index 000000000..3c49eb51f --- /dev/null +++ b/test_regress/t/t_covergroup_clocked_sample.out @@ -0,0 +1,4 @@ +cg.cp_data.one: 1 +cg.cp_data.three: 1 +cg.cp_data.two: 1 +cg.cp_data.zero: 2 diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py new file mode 100755 index 000000000..ceec4c59e --- /dev/null +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_clocked_sample.v b/test_regress/t/t_covergroup_clocked_sample.v new file mode 100644 index 000000000..22a58079d --- /dev/null +++ b/test_regress/t/t_covergroup_clocked_sample.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Test covergroup clocked (automatic) sampling +// Tests --no-timing (default) mode; see t_covergroup_auto_sample_timing for --timing variant. +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// 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; + 1: data <= 2'b01; + 2: data <= 2'b10; + 3: data <= 2'b11; + 4: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase + + // NOTE: NO manual .sample() call - relying on automatic sampling! + end +endmodule diff --git a/test_regress/t/t_covergroup_coverage_query.out b/test_regress/t/t_covergroup_coverage_query.out new file mode 100644 index 000000000..fd62c5ca3 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_query.out @@ -0,0 +1,3 @@ +cg.data.high: 1 +cg.data.low: 1 +cg.data.mid: 1 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_query.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..596a3d815 --- /dev/null +++ b/test_regress/t/t_covergroup_coverage_query.v @@ -0,0 +1,44 @@ +// 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*/); + 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; + + // Sample low bin - should be 33.3% (1 of 3 bins) + data = 1; + cg_inst.sample(); + $display("After low: %0.1f%%", cg_inst.get_inst_coverage()); + + // Sample mid bin - should be 66.7% (2 of 3 bins) + data = 5; + cg_inst.sample(); + $display("After mid: %0.1f%%", cg_inst.get_inst_coverage()); + + // Sample high bin - should be 100.0% (3 of 3 bins) + data = 10; + cg_inst.sample(); + $display("After high: %0.1f%%", cg_inst.get_inst_coverage()); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_coverpoint_method_unsup.out b/test_regress/t/t_covergroup_coverpoint_method_unsup.out new file mode 100644 index 000000000..dc98d2879 --- /dev/null +++ b/test_regress/t/t_covergroup_coverpoint_method_unsup.out @@ -0,0 +1,40 @@ +%Warning-COVERIGN: t/t_covergroup_coverpoint_method_unsup.v:21:32: Unsupported: 'bins' explicit array size (treated as '[]') + 21 | coverpoint b {bins the_bins[5] = {[0 : 20]};} + | ^ + ... 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_coverpoint_method_unsup.v:31:42: Member 'a' not found in covergroup 'cg' + : ... note: In instance 't' + 31 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error-UNSUPPORTED: t/t_covergroup_coverpoint_method_unsup.v:31:44: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' + : ... note: In instance 't' + 31 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); + | ^~~~~~~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: t/t_covergroup_coverpoint_method_unsup.v:32:42: Member 'b' not found in covergroup 'cg' + : ... note: In instance 't' + 32 | $display("coverage b = %f", the_cg.b.get_inst_coverage()); + | ^ +%Error-UNSUPPORTED: t/t_covergroup_coverpoint_method_unsup.v:32:44: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' + : ... note: In instance 't' + 32 | $display("coverage b = %f", the_cg.b.get_inst_coverage()); + | ^~~~~~~~~~~~~~~~~ +%Error: t/t_covergroup_coverpoint_method_unsup.v:33:18: Member 'a' not found in covergroup 'cg' + : ... note: In instance 't' + 33 | if (the_cg.a.get_inst_coverage() != 15 / 16.0) $stop(); + | ^ +%Error-UNSUPPORTED: t/t_covergroup_coverpoint_method_unsup.v:33:20: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' + : ... note: In instance 't' + 33 | if (the_cg.a.get_inst_coverage() != 15 / 16.0) $stop(); + | ^~~~~~~~~~~~~~~~~ +%Error: t/t_covergroup_coverpoint_method_unsup.v:34:18: Member 'b' not found in covergroup 'cg' + : ... note: In instance 't' + 34 | if (the_cg.b.get_inst_coverage() != 4 / 5.0) $stop(); + | ^ +%Error-UNSUPPORTED: t/t_covergroup_coverpoint_method_unsup.v:34:20: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' + : ... note: In instance 't' + 34 | if (the_cg.b.get_inst_coverage() != 4 / 5.0) $stop(); + | ^~~~~~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_coverpoints_unsup.py b/test_regress/t/t_covergroup_coverpoint_method_unsup.py similarity index 100% rename from test_regress/t/t_covergroup_coverpoints_unsup.py rename to test_regress/t/t_covergroup_coverpoint_method_unsup.py diff --git a/test_regress/t/t_covergroup_coverpoints_unsup.v b/test_regress/t/t_covergroup_coverpoint_method_unsup.v similarity index 100% rename from test_regress/t/t_covergroup_coverpoints_unsup.v rename to test_regress/t/t_covergroup_coverpoint_method_unsup.v diff --git a/test_regress/t/t_covergroup_coverpoints_unsup.out b/test_regress/t/t_covergroup_coverpoints_unsup.out deleted file mode 100644 index 10fe3461d..000000000 --- a/test_regress/t/t_covergroup_coverpoints_unsup.out +++ /dev/null @@ -1,52 +0,0 @@ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:19:17: Ignoring unsupported: coverage clocking event - 19 | 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:20:5: Ignoring unsupported: coverpoint - 20 | coverpoint a; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:21:36: Ignoring unsupported: cover bin specification - 21 | coverpoint b {bins the_bins[5] = {[0 : 20]};} - | ^ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:21:5: Ignoring unsupported: coverpoint - 21 | coverpoint b {bins the_bins[5] = {[0 : 20]};} - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_coverpoints_unsup.v:19:3: Ignoring unsupported: covergroup - 19 | covergroup cg @(posedge clk); - | ^~~~~~~~~~ -%Error: t/t_covergroup_coverpoints_unsup.v:31:42: Member 'a' not found in covergroup 'cg' - : ... note: In instance 't' - 31 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); - | ^ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_covergroup_coverpoints_unsup.v:31:44: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 31 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); - | ^~~~~~~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: t/t_covergroup_coverpoints_unsup.v:32:42: Member 'b' not found in covergroup 'cg' - : ... note: In instance 't' - 32 | $display("coverage b = %f", the_cg.b.get_inst_coverage()); - | ^ -%Error-UNSUPPORTED: t/t_covergroup_coverpoints_unsup.v:32:44: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 32 | $display("coverage b = %f", the_cg.b.get_inst_coverage()); - | ^~~~~~~~~~~~~~~~~ -%Error: t/t_covergroup_coverpoints_unsup.v:33:18: Member 'a' not found in covergroup 'cg' - : ... note: In instance 't' - 33 | if (the_cg.a.get_inst_coverage() != 15 / 16.0) $stop(); - | ^ -%Error-UNSUPPORTED: t/t_covergroup_coverpoints_unsup.v:33:20: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 33 | if (the_cg.a.get_inst_coverage() != 15 / 16.0) $stop(); - | ^~~~~~~~~~~~~~~~~ -%Error: t/t_covergroup_coverpoints_unsup.v:34:18: Member 'b' not found in covergroup 'cg' - : ... note: In instance 't' - 34 | if (the_cg.b.get_inst_coverage() != 4 / 5.0) $stop(); - | ^ -%Error-UNSUPPORTED: t/t_covergroup_coverpoints_unsup.v:34:20: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 34 | if (the_cg.b.get_inst_coverage() != 4 / 5.0) $stop(); - | ^~~~~~~~~~~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out new file mode 100644 index 000000000..0a1052f79 --- /dev/null +++ b/test_regress/t/t_covergroup_cross.out @@ -0,0 +1,84 @@ +cg2.addr_cmd.addr0_x_read [cross]: 1 +cg2.addr_cmd.addr0_x_write [cross]: 1 +cg2.addr_cmd.addr1_x_read [cross]: 1 +cg2.addr_cmd.addr1_x_write [cross]: 1 +cg2.cp_addr.addr0: 2 +cg2.cp_addr.addr1: 2 +cg2.cp_cmd.read: 2 +cg2.cp_cmd.write: 2 +cg3.addr_cmd_mode.addr0_x_read_x_debug [cross]: 0 +cg3.addr_cmd_mode.addr0_x_read_x_normal [cross]: 1 +cg3.addr_cmd_mode.addr0_x_write_x_debug [cross]: 1 +cg3.addr_cmd_mode.addr0_x_write_x_normal [cross]: 0 +cg3.addr_cmd_mode.addr1_x_read_x_debug [cross]: 0 +cg3.addr_cmd_mode.addr1_x_read_x_normal [cross]: 0 +cg3.addr_cmd_mode.addr1_x_write_x_debug [cross]: 0 +cg3.addr_cmd_mode.addr1_x_write_x_normal [cross]: 1 +cg3.addr_cmd_mode.addr2_x_read_x_debug [cross]: 1 +cg3.addr_cmd_mode.addr2_x_read_x_normal [cross]: 0 +cg3.addr_cmd_mode.addr2_x_write_x_debug [cross]: 0 +cg3.addr_cmd_mode.addr2_x_write_x_normal [cross]: 0 +cg3.cp_addr.addr0: 2 +cg3.cp_addr.addr1: 1 +cg3.cp_addr.addr2: 1 +cg3.cp_cmd.read: 2 +cg3.cp_cmd.write: 2 +cg3.cp_mode.debug: 2 +cg3.cp_mode.normal: 2 +cg4.addr_cmd_mode_parity.addr0_x_read_x_debug_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr0_x_read_x_debug_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr0_x_read_x_normal_x_even [cross]: 1 +cg4.addr_cmd_mode_parity.addr0_x_read_x_normal_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr0_x_write_x_debug_x_even [cross]: 1 +cg4.addr_cmd_mode_parity.addr0_x_write_x_debug_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr0_x_write_x_normal_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr0_x_write_x_normal_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_read_x_debug_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_read_x_debug_x_odd [cross]: 1 +cg4.addr_cmd_mode_parity.addr1_x_read_x_normal_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_read_x_normal_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_write_x_debug_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_write_x_debug_x_odd [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_write_x_normal_x_even [cross]: 0 +cg4.addr_cmd_mode_parity.addr1_x_write_x_normal_x_odd [cross]: 1 +cg4.cp_addr.addr0: 2 +cg4.cp_addr.addr1: 2 +cg4.cp_cmd.read: 2 +cg4.cp_cmd.write: 2 +cg4.cp_mode.debug: 2 +cg4.cp_mode.normal: 2 +cg4.cp_parity.even: 2 +cg4.cp_parity.odd: 2 +cg5.addr_cmd_opt.addr0_x_read [cross]: 1 +cg5.addr_cmd_opt.addr0_x_write [cross]: 0 +cg5.addr_cmd_opt.addr1_x_read [cross]: 0 +cg5.addr_cmd_opt.addr1_x_write [cross]: 1 +cg5.cp_addr.addr0: 1 +cg5.cp_addr.addr1: 1 +cg5.cp_cmd.read: 1 +cg5.cp_cmd.write: 1 +cg_ignore.cp_addr.a0: 2 +cg_ignore.cp_addr.a1: 2 +cg_ignore.cp_addr.ign [ignore]: 1 +cg_ignore.cp_cmd.read: 3 +cg_ignore.cp_cmd.write: 2 +cg_ignore.cross_ab.a0_x_read [cross]: 1 +cg_ignore.cross_ab.a0_x_write [cross]: 1 +cg_ignore.cross_ab.a1_x_read [cross]: 1 +cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 +cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 +cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 +cg_range.addr_cmd_range.lo_range_x_write [cross]: 1 +cg_range.cp_addr.hi_range: 2 +cg_range.cp_addr.lo_range: 2 +cg_range.cp_cmd.read: 2 +cg_range.cp_cmd.write: 2 +cg_unnamed_cross.__cross7.a0_x_read [cross]: 1 +cg_unnamed_cross.__cross7.a0_x_write [cross]: 0 +cg_unnamed_cross.__cross7.a1_x_read [cross]: 0 +cg_unnamed_cross.__cross7.a1_x_write [cross]: 1 +cg_unnamed_cross.cp_a.a0: 1 +cg_unnamed_cross.cp_a.a1: 1 +cg_unnamed_cross.cp_c.read: 1 +cg_unnamed_cross.cp_c.write: 1 diff --git a/test_regress/t/t_covergroup_cross.py b/test_regress/t/t_covergroup_cross.py new file mode 100755 index 000000000..dbc7c1913 --- /dev/null +++ b/test_regress/t/t_covergroup_cross.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=['--coverage --dumpi-tree 3 --dumpi-tree-json 3']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v new file mode 100644 index 000000000..bab587ff1 --- /dev/null +++ b/test_regress/t/t_covergroup_cross.v @@ -0,0 +1,169 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test cross coverage: 2-way, 3-way, and 4-way crosses + +module t; + logic [1:0] addr; + logic cmd; + logic mode; + logic parity; + + // 2-way cross + covergroup cg2; + cp_addr: coverpoint addr { + bins addr0 = {0}; + bins addr1 = {1}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + addr_cmd: cross cp_addr, cp_cmd; + endgroup + + // 3-way cross + covergroup cg3; + 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}; + } + addr_cmd_mode: cross cp_addr, cp_cmd, cp_mode; + endgroup + + // 4-way cross + covergroup cg4; + 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}; + } + addr_cmd_mode_parity: cross cp_addr, cp_cmd, cp_mode, cp_parity; + endgroup + + // Cross with option inside body: exercises addOptionsp in visit(AstCoverCross*) + covergroup cg5; + cp_addr: coverpoint addr { + bins addr0 = {0}; + bins addr1 = {1}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + addr_cmd_opt: cross cp_addr, cp_cmd { + option.weight = 2; + } + endgroup + + // 2-way cross with range bin: exercises lo!=hi path in buildBinCondition + covergroup cg_range; + cp_addr: coverpoint addr { + bins lo_range = {[0:1]}; // range bin (lo != hi) -> makeRangeCondition path + bins hi_range = {[2:3]}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + addr_cmd_range: cross cp_addr, cp_cmd; + endgroup + + // Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch + // in collectBins during cross code generation (L1139) + covergroup cg_ignore; + cp_addr: coverpoint addr { + ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path + bins a0 = {0}; + bins a1 = {1}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + cross_ab: cross cp_addr, cp_cmd; + endgroup + + // Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395) + covergroup cg_unnamed_cross; + cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; } + cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; } + cross cp_a, cp_c; // no label -> crossName is empty + endgroup + + cg2 cg2_inst = new; + cg_ignore cg_ignore_inst = new; + cg_range cg_range_inst = new; + cg3 cg3_inst = new; + cg4 cg4_inst = new; + cg5 cg5_inst = new; + cg_unnamed_cross cg_unnamed_cross_inst = new; + + initial begin + // Sample 2-way: hit all 4 combinations + addr = 0; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x read + addr = 1; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x write + addr = 0; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x write + addr = 1; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x read + + // Sample 3-way: hit 4 of 12 combinations + addr = 0; cmd = 0; mode = 0; cg3_inst.sample(); // addr0 x read x normal + addr = 1; cmd = 1; mode = 0; cg3_inst.sample(); // addr1 x write x normal + addr = 2; cmd = 0; mode = 1; cg3_inst.sample(); // addr2 x read x debug + addr = 0; cmd = 1; mode = 1; cg3_inst.sample(); // addr0 x write x debug + + // Sample 4-way: hit 4 of 16 combinations + addr = 0; cmd = 0; mode = 0; parity = 0; cg4_inst.sample(); + addr = 1; cmd = 1; mode = 0; parity = 1; cg4_inst.sample(); + addr = 0; cmd = 1; mode = 1; parity = 0; cg4_inst.sample(); + addr = 1; cmd = 0; mode = 1; parity = 1; cg4_inst.sample(); + + // Sample cg5 (cross with option) + addr = 0; cmd = 0; cg5_inst.sample(); + addr = 1; cmd = 1; cg5_inst.sample(); + + // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it + addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read + addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write + addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write + addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read + addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins) + + // Sample range-bin cross + addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read + addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write + addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write + addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read + + // Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross") + addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read + addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write + + $write("*-* All Finished *-*\n"); + $finish; + 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..bf643fc15 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_large_main.cpp @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-FileCopyrightText: 2024 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_database.py b/test_regress/t/t_covergroup_database.py new file mode 100755 index 000000000..2748786a8 --- /dev/null +++ b/test_regress/t/t_covergroup_database.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') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +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') + +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..ec38554ef --- /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.out b/test_regress/t/t_covergroup_default_bins.out new file mode 100644 index 000000000..188f4ae58 --- /dev/null +++ b/test_regress/t/t_covergroup_default_bins.out @@ -0,0 +1,20 @@ +cg.data.high: 1 +cg.data.low: 1 +cg.data.other: 2 +cg2.cp_only_default.all: 4 +cg3.data.bad [ignore]: 1 +cg3.data.err [illegal]: 0 +cg3.data.normal: 2 +cg3.data.other: 2 +cg4.cp_idx.auto_0: 1 +cg4.cp_idx.auto_1: 1 +cg4.cp_idx.auto_2: 1 +cg4.cp_idx.skip [ignore]: 0 +cg5.cp_data64.auto[0]: 2 +cg5.cp_data64.auto[1]: 0 +cg5.cp_data64.auto[2]: 0 +cg5.cp_data64.auto[3]: 0 +cg6.cp_data65.hi: 1 +cg6.cp_data65.lo: 1 +cg7.data.hi: 1 +cg7.data.lo: 1 diff --git a/test_regress/t/t_covergroup_default_bins.py b/test_regress/t/t_covergroup_default_bins.py new file mode 100755 index 000000000..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_default_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() 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..b2142d417 --- /dev/null +++ b/test_regress/t/t_covergroup_default_bins.v @@ -0,0 +1,139 @@ +// 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 + +// Non-covergroup class: exercises V3Active isCovergroup()=false branch +class DataHelper; + bit [7:0] val; + function new(bit [7:0] v); val = v; endfunction +endclass + +module t; + bit [7:0] data; + logic [1:0] idx; + logic [63:0] data64; // 64-bit: exercises width>=64 auto-bin path (L139) + logic [64:0] data65; // 65-bit: exercises exprWidth>64 in makeRangeCondition + DataHelper helper; // Module-level class var: exercises V3Active isCovergroup()=false + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins high = {[12:15]}; + bins other = default; // Catches everything else (4-11, 16+) + } + endgroup + + // Covergroup with default as the ONLY bin: exercises defaultCondp=BitTrue path + covergroup cg2; + cp_only_default: coverpoint data { + bins all = default; + } + endgroup + + // Covergroup with default + ignore + illegal bins: exercises BINS_IGNORE/BINS_ILLEGAL + // skip paths in generateDefaultBinMatchCode (L558-L559) + covergroup cg3; + coverpoint data { + ignore_bins bad = {255}; // BINS_IGNORE skip path + illegal_bins err = {254}; // BINS_ILLEGAL skip path + bins normal = {[1:10]}; + bins other = default; + } + endgroup + + // Covergroup with auto-bins + ignore_bins on small range: exercises L295 excluded-value continue + // When numValidValues <= auto_bin_max, single-value auto-bins are created per value; the + // excluded.find() check at L295 fires for the ignore_bins value (idx=2). + covergroup cg4; + cp_idx: coverpoint idx { + ignore_bins skip = {2}; // value 2 excluded; auto-bins created for 0,1,3 + } + endgroup + + // 64-bit signal with 4 auto-bins: exercises width>=64 branch in auto-bin range calculation + covergroup cg5; + cp_data64: coverpoint data64 { bins auto[4]; } + endgroup + + // 65-bit signal with range bins: exercises exprWidth>64 path in makeRangeCondition + covergroup cg6; + cp_data65: coverpoint data65 { bins lo = {[0:15]}; bins hi = {[100:200]}; } + endgroup + + // Unlabeled coverpoint: exercises cpName fallback via exprp()->name() (L1394-1398) + covergroup cg7; + coverpoint data { bins lo = {[0:7]}; bins hi = {[8:15]}; } + endgroup + + initial begin + cg cg_inst; + cg2 cg2_inst; + cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + cg6 cg6_inst; + cg7 cg7_inst; + + cg_inst = new(); + cg2_inst = new(); + cg3_inst = new(); + cg4_inst = new(); + cg5_inst = new(); + cg6_inst = new(); + cg7_inst = new(); + helper = new(8'h42); + data = helper.val; // Use helper to avoid optimization + + // Hit low bin + data = 2; + cg_inst.sample(); + cg2_inst.sample(); + + // Hit high bin + data = 14; + cg_inst.sample(); + cg2_inst.sample(); + + // Hit default bin with value 7 (not in low or high) + data = 7; + cg_inst.sample(); + cg2_inst.sample(); + + // Hit another default value (should not increase coverage) + data = 20; + cg_inst.sample(); + cg2_inst.sample(); + + // Sample cg3: exercises BINS_IGNORE/BINS_ILLEGAL skip in default-bin detection loop + data = 2; cg3_inst.sample(); // hits normal bin + data = 7; cg3_inst.sample(); // hits normal bin again + data = 255; cg3_inst.sample(); // ignore_bins (not counted) + // note: do not sample 254 (illegal_bins would cause runtime assertion) + data = 100; cg3_inst.sample(); // hits default (other) bin + + // Sample cg4: exercises auto-bin generation with excluded value (L295) + // idx=2 is in ignore_bins, so auto-bins cover 0,1,3 only + idx = 0; cg4_inst.sample(); + idx = 1; cg4_inst.sample(); + idx = 3; cg4_inst.sample(); + + // Sample cg5: 64-bit signal, sample into bin b[0] (value 0 is in first quarter) + data64 = 0; cg5_inst.sample(); + data64 = 5; cg5_inst.sample(); + + // Sample cg6: 65-bit signal with range bins + data65 = 5; cg6_inst.sample(); // hits bin lo=[0:15] + data65 = 150; cg6_inst.sample(); // hits bin hi=[100:200] + + // Sample cg7: unlabeled coverpoint (exercises exprp()->name() path) + data = 3; cg7_inst.sample(); // hits bin lo + data = 10; cg7_inst.sample(); // hits bin hi + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_dynamic.out b/test_regress/t/t_covergroup_dynamic.out new file mode 100644 index 000000000..9f7d6e4c9 --- /dev/null +++ b/test_regress/t/t_covergroup_dynamic.out @@ -0,0 +1,2 @@ +cg.data.high: 1 +cg.data.low: 2 diff --git a/test_regress/t/t_covergroup_dynamic.py b/test_regress/t/t_covergroup_dynamic.py new file mode 100755 index 000000000..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_dynamic.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_dynamic.v b/test_regress/t/t_covergroup_dynamic.v new file mode 100644 index 000000000..26fa8169c --- /dev/null +++ b/test_regress/t/t_covergroup_dynamic.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, 2024 by Wilson Snyder. +// SPDX-FileCopyrightText: 2024 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; + + // Create first dynamic instance + cg_inst = new; + data = 0; cg_inst.sample(); // low bin + data = 2; cg_inst.sample(); // high bin + + // Create second independent instance + begin + cg cg2; + cg2 = new; + data = 0; cg2.sample(); // low bin + end + + $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..f25482bde --- /dev/null +++ b/test_regress/t/t_covergroup_empty.cpp @@ -0,0 +1,29 @@ +// -*- 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-FileCopyrightText: 2026 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.out b/test_regress/t/t_covergroup_empty.out new file mode 100644 index 000000000..fad58640a --- /dev/null +++ b/test_regress/t/t_covergroup_empty.out @@ -0,0 +1,2 @@ +Empty covergroup coverage: 100.000000% +*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_empty.py b/test_regress/t/t_covergroup_empty.py new file mode 100755 index 000000000..27bc569af --- /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(expect_filename=test.golden_filename) + +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..abdc72b22 --- /dev/null +++ b/test_regress/t/t_covergroup_empty.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module - Edge case: empty covergroup +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// 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 + real cov; + cov = cg_inst.get_inst_coverage(); + $display("Empty covergroup coverage: %f%%", cov); + $write("*-* All Finished *-*\n"); + $finish; + end + + if (cyc > 10) begin + $display("ERROR: Test timed out"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_extends.v b/test_regress/t/t_covergroup_extends.v deleted file mode 100644 index 880857d4b..000000000 --- a/test_regress/t/t_covergroup_extends.v +++ /dev/null @@ -1,39 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -module t; - class base; - enum {red, green, blue} color; - covergroup g1 (bit [3:0] a) with function sample(bit b); - option.weight = 10; - option.per_instance = 1; - coverpoint a; - coverpoint b; - c: coverpoint color; - endgroup - function new(); - g1 = new(0); - endfunction - endclass - - class derived extends base; - bit d; - covergroup extends g1; - option.weight = 1; // overrides the weight from base g1 - // uses per_instance = 1 from base g1 - c: coverpoint color // overrides the c coverpoint in base g1 - { - ignore_bins ignore = {blue}; - } - coverpoint d; // adds new coverpoint - cross a, d; // crosses new coverpoint with inherited one - endgroup :g1 - function new(); - super.new(); - endfunction - endclass -endmodule diff --git a/test_regress/t/t_covergroup_extends_newfirst.out b/test_regress/t/t_covergroup_extends_newfirst.out new file mode 100644 index 000000000..2e436f2ef --- /dev/null +++ b/test_regress/t/t_covergroup_extends_newfirst.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_covergroup_extends_newfirst.v:29:5: Unsupported: covergroup inheritance (extends) is not implemented + 29 | covergroup extends g1; + | ^~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_extends_unsup.out b/test_regress/t/t_covergroup_extends_unsup.out new file mode 100644 index 000000000..6da853f7c --- /dev/null +++ b/test_regress/t/t_covergroup_extends_unsup.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_covergroup_extends_unsup.v:26:5: Unsupported: covergroup inheritance (extends) + 26 | covergroup extends g1; + | ^~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_extends.py b/test_regress/t/t_covergroup_extends_unsup.py similarity index 88% rename from test_regress/t/t_covergroup_extends.py rename to test_regress/t/t_covergroup_extends_unsup.py index 10ad7f0de..ef7407f24 100755 --- a/test_regress/t/t_covergroup_extends.py +++ b/test_regress/t/t_covergroup_extends_unsup.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +test.lint(expect_filename=test.golden_filename, fails=True) test.passes() diff --git a/test_regress/t/t_covergroup_extends_newfirst.v b/test_regress/t/t_covergroup_extends_unsup.v similarity index 94% rename from test_regress/t/t_covergroup_extends_newfirst.v rename to test_regress/t/t_covergroup_extends_unsup.v index aee7a2f7b..f1240cd4b 100644 --- a/test_regress/t/t_covergroup_extends_newfirst.v +++ b/test_regress/t/t_covergroup_extends_unsup.v @@ -1,15 +1,13 @@ // DESCRIPTION: Verilator: Verilog Test module // +// Covergroup inheritance with 'extends' is not yet supported +// // This file ONLY is placed under the Creative Commons Public Domain // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 -/* verilator lint_off COVERIGN */ module t; class base; - function new(); - g1 = new(0); - endfunction enum {red, green, blue} color; covergroup g1 (bit [3:0] a) with function sample(bit b); option.weight = 10; @@ -18,13 +16,13 @@ module t; coverpoint b; c: coverpoint color; endgroup + function new(); + g1 = new(0); + endfunction endclass class derived extends base; bit d; - function new(); - super.new(); - endfunction covergroup extends g1; option.weight = 1; // overrides the weight from base g1 // uses per_instance = 1 from base g1 @@ -35,5 +33,8 @@ module t; coverpoint d; // adds new coverpoint cross a, d; // crosses new coverpoint with inherited one endgroup :g1 + function new(); + super.new(); + endfunction endclass endmodule diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out new file mode 100644 index 000000000..5ce6817a0 --- /dev/null +++ b/test_regress/t/t_covergroup_iff.out @@ -0,0 +1,9 @@ +cg_array_iff.cp.arr: 1 +cg_default_iff.cp.def: 1 +cg_default_iff.cp.known: 1 +cg_iff.cp_value.disabled_hi: 0 +cg_iff.cp_value.disabled_lo: 0 +cg_iff.cp_value.enabled_hi: 1 +cg_iff.cp_value.enabled_lo: 1 +cg_trans2_iff.cp.t2: 1 +cg_trans3_iff.cp.t3: 1 diff --git a/test_regress/t/t_covergroup_iff.py b/test_regress/t/t_covergroup_iff.py new file mode 100755 index 000000000..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_iff.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..256522a08 --- /dev/null +++ b/test_regress/t/t_covergroup_iff.v @@ -0,0 +1,107 @@ +// 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-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test iff (enable) guard: sampling is gated by the enable condition. +// Covers iff on explicit value bins, default bin, array bins, +// simple 2-step transition, and 3-step transition. + +module t; + logic enable; + int value; + + // --- Original: iff on explicit value bins (lines 565-567) --- + covergroup cg_iff; + cp_value: coverpoint value iff (enable) { + bins disabled_lo = {1}; + bins disabled_hi = {2}; + bins enabled_lo = {3}; + bins enabled_hi = {4}; + } + endgroup + + // --- iff on default bin (lines 633-635/641) --- + covergroup cg_default_iff; + cp: coverpoint value iff (enable) { + bins known = {10}; + bins def = default; // default bin with coverpoint-level iff + } + endgroup + + // --- iff on array bins (lines 982-983) --- + covergroup cg_array_iff; + cp: coverpoint value iff (enable) { + bins arr[] = {5, 6, 7}; // array bins, all gated by iff + } + endgroup + + // --- iff on 2-step transition (lines 1109-1110) --- + covergroup cg_trans2_iff; + cp: coverpoint value iff (enable) { + bins t2 = (1 => 2); + } + endgroup + + // --- iff on 3-step transition (lines 724-725, 762-763) --- + covergroup cg_trans3_iff; + cp: coverpoint value iff (enable) { + bins t3 = (1 => 2 => 3); + } + endgroup + + cg_iff cg1 = new; + cg_default_iff cg2 = new; + cg_array_iff cg3 = new; + cg_trans2_iff cg4 = new; + cg_trans3_iff cg5 = new; + + initial begin + // Sample disabled_lo and disabled_hi with enable=0 -- must not be recorded + enable = 0; + value = 1; cg1.sample(); + value = 2; cg1.sample(); + + // Sample enabled_lo and enabled_hi with enable=1 -- must be recorded + enable = 1; + value = 3; cg1.sample(); + value = 4; cg1.sample(); + + // cg2: default bin -- enable=1 lets known and default through + enable = 1; + value = 10; cg2.sample(); // hits 'known' + value = 99; cg2.sample(); // hits 'def' (default) + enable = 0; + value = 99; cg2.sample(); // gated by iff -- must NOT hit 'def' + + // cg3: array bins with iff + enable = 1; + value = 5; cg3.sample(); // arr[5] hit + enable = 0; + value = 6; cg3.sample(); // gated + + // cg4: 2-step transition with iff + enable = 1; + value = 1; cg4.sample(); + value = 2; cg4.sample(); // (1=>2) hit with enable=1 + enable = 0; + value = 1; cg4.sample(); + value = 2; cg4.sample(); // (1=>2) gated by iff + + // cg5: 3-step transition with iff + enable = 1; + value = 1; cg5.sample(); + value = 2; cg5.sample(); // mid-sequence, enable=1 + enable = 0; + value = 3; cg5.sample(); // iff fails at step 3 -- triggers restart path (line 762-763) + enable = 1; + value = 1; cg5.sample(); + value = 2; cg5.sample(); + value = 3; cg5.sample(); // (1=>2=>3) fully hit with enable=1 + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out new file mode 100644 index 000000000..dc00cfc0b --- /dev/null +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -0,0 +1,12 @@ +cg.data.catch_all [ignore]: 0 +cg.data.high: 1 +cg.data.low: 1 +cg.data.reserved [ignore]: 1 +cg.data.wib: 0 +cg2.cp_auto.auto_0: 1 +cg2.cp_auto.auto_1: 1 +cg2.cp_auto.ign [ignore]: 2 +cg2.cp_auto.ign_trans [ignore]: 1 +cg2.cp_bounds.hi: 2 +cg2.cp_bounds.lo: 2 +cg2.cp_full.all: 4 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_ignore_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..fd270beca --- /dev/null +++ b/test_regress/t/t_covergroup_ignore_bins.v @@ -0,0 +1,62 @@ +// 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*/); + logic [3:0] data; + logic [1:0] data2; // 2-bit signal for range-boundary tests + + covergroup cg; + coverpoint data { + bins low = {[0:3]}; + bins high = {[8:11]}; + ignore_bins reserved = {[12:15]}; + ignore_bins catch_all = default; // null rangesp: exercises generateBinMatchCode !fullCondp + wildcard ignore_bins wib = {4'b1?00}; // wildcard ignore bins (L7084-7085) + } + endgroup + + // Exercises: + // extractValuesFromRange AstInsideRange branch (ignore_bins range, no regular bins) + // createImplicitAutoBins with excluded range values + // makeRangeCondition: skipUpperCheck=true (hi=maxVal) and both-skip (BitTrue) + // ignore_bins with transition list (L7102-7104) + covergroup cg2; + cp_auto: coverpoint data2 { + ignore_bins ign = {[2:3]}; // range ignore, no regular bins -> auto-bins created + ignore_bins ign_trans = (0 => 1); // ignore_bins with transition (L7102-7104) + } + cp_bounds: coverpoint data2 { + bins lo = {[0:1]}; // lo=0: skipLowerCheck -> AstLte + bins hi = {[2:3]}; // hi=maxVal (2-bit): skipUpperCheck -> AstGte + } + cp_full: coverpoint data2 { + bins all = {[0:3]}; // lo=0 and hi=maxVal: both skip -> AstConst(BitTrue) + } + endgroup + + cg cg_inst; + cg2 cg2_inst; + + initial begin + cg_inst = new; + cg2_inst = new; + + data = 13; cg_inst.sample(); // reserved - ignored + data = 1; cg_inst.sample(); // low + data = 10; cg_inst.sample(); // high + + data2 = 0; cg2_inst.sample(); // auto_0, lo, all + data2 = 1; cg2_inst.sample(); // auto_1, lo, all + data2 = 2; cg2_inst.sample(); // ign, hi, all + data2 = 3; cg2_inst.sample(); // ign, hi, all + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out new file mode 100644 index 000000000..6355322b5 --- /dev/null +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -0,0 +1,11 @@ +cg.data.forbidden [illegal]: 0 +cg.data.high: 1 +cg.data.low: 1 +cg.data.mid: 1 +cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.ok: 1 +cg2.cp_arr.wlib: 0 +cg2.cp_trans.bad_2step [illegal]: 0 +cg2.cp_trans.bad_3step [illegal]: 0 +cg2.cp_trans.lib_default [illegal]: 0 +cg2.cp_trans.ok: 1 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_illegal_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..31c837ee6 --- /dev/null +++ b/test_regress/t/t_covergroup_illegal_bins.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test that illegal_bins are excluded from coverage (like ignore_bins) +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [1:0] data; + logic [3:0] data4; + + covergroup cg; + coverpoint data { + bins low = {0}; + bins mid = {1}; + bins high = {2}; + illegal_bins forbidden = {3}; + } + endgroup + + // cg2: exercises illegal_bins with 3-step transition (lines 744-747) and + // illegal_bins array notation (lines 996-998) + covergroup cg2; + cp_trans: coverpoint data4 { + bins ok = {0}; + illegal_bins bad_2step = (1 => 2); // 2-step illegal transition (simple path) + illegal_bins bad_3step = (1 => 2 => 3); // multi-step illegal transition + illegal_bins lib_default = default; // illegal_bins = default (L7123-7124) + } + cp_arr: coverpoint data4 { + bins ok = {0}; + illegal_bins bad_arr[] = {8, 9, 10}; // illegal array bins + wildcard illegal_bins wlib = {4'b1?00}; // wildcard illegal bins (L7087-7088) + } + endgroup + + initial begin + automatic cg cg_inst = new; + automatic cg2 cg2_inst = new; + + // Sample legal values only + data = 0; cg_inst.sample(); + data = 1; cg_inst.sample(); + data = 2; cg_inst.sample(); + + // Sample cg2 - only safe values, never triggering illegal bins + data4 = 0; cg2_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_in_class.v b/test_regress/t/t_covergroup_in_class.v deleted file mode 100644 index 4ab0f383a..000000000 --- a/test_regress/t/t_covergroup_in_class.v +++ /dev/null @@ -1,19 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -class myClass; - covergroup embeddedCg; - - endgroup - - function new(); - real r; - embeddedCg = new(); - embeddedCg.sample(); - r = embeddedCg.get_coverage(); - endfunction -endclass 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_in_class.py b/test_regress/t/t_covergroup_in_class_namespace.py similarity index 90% rename from test_regress/t/t_covergroup_in_class.py rename to test_regress/t/t_covergroup_in_class_namespace.py index 10ad7f0de..9ffd45286 100755 --- a/test_regress/t/t_covergroup_in_class.py +++ b/test_regress/t/t_covergroup_in_class_namespace.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +test.compile(verilator_flags2=['--coverage']) test.passes() diff --git a/test_regress/t/t_covergroup_in_class_colliding.v b/test_regress/t/t_covergroup_in_class_namespace.v similarity index 79% rename from test_regress/t/t_covergroup_in_class_colliding.v rename to test_regress/t/t_covergroup_in_class_namespace.v index 5760dc019..0b55b38b6 100644 --- a/test_regress/t/t_covergroup_in_class_colliding.v +++ b/test_regress/t/t_covergroup_in_class_namespace.v @@ -4,17 +4,15 @@ // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 -/* verilator lint_off COVERIGN */ class myClass; covergroup embeddedCg; endgroup function new(); - real r; embeddedCg = new(); embeddedCg.sample(); - r = embeddedCg.get_coverage(); + void'(embeddedCg.get_coverage()); endfunction endclass @@ -24,9 +22,8 @@ class secondClass; endgroup function new(); - real r; embeddedCg = new(); embeddedCg.sample(); - r = embeddedCg.get_coverage(); + void'(embeddedCg.get_coverage()); endfunction endclass diff --git a/test_regress/t/t_covergroup_in_class_with_sample.py b/test_regress/t/t_covergroup_in_class_with_sample.py deleted file mode 100755 index 10ad7f0de..000000000 --- a/test_regress/t/t_covergroup_in_class_with_sample.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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.compile() - -test.passes() diff --git a/test_regress/t/t_covergroup_in_class_with_sample.v b/test_regress/t/t_covergroup_in_class_with_sample.v deleted file mode 100644 index df7a4611a..000000000 --- a/test_regress/t/t_covergroup_in_class_with_sample.v +++ /dev/null @@ -1,15 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -class C; - covergroup embedded(int x) with function sample (int a, bit b); - endgroup - function new(); - embedded = new(1); - embedded.sample(2, 1'b0); - endfunction -endclass diff --git a/test_regress/t/t_covergroup_member_event_unsup.out b/test_regress/t/t_covergroup_member_event_unsup.out new file mode 100644 index 000000000..7f093d75c --- /dev/null +++ b/test_regress/t/t_covergroup_member_event_unsup.out @@ -0,0 +1,7 @@ +%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:11:5: Unsupported: 'covergroup' clocking event on member variable + : ... note: In instance 't' + 11 | covergroup cov1 @m_z; + | ^~~~~~~~~~ + ... 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_bad2.py b/test_regress/t/t_covergroup_member_event_unsup.py similarity index 77% rename from test_regress/t/t_covergroup_option_bad2.py rename to test_regress/t/t_covergroup_member_event_unsup.py index 4d0f745d0..b5718946c 100755 --- a/test_regress/t/t_covergroup_option_bad2.py +++ b/test_regress/t/t_covergroup_member_event_unsup.py @@ -11,8 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(expect_filename=test.golden_filename, - verilator_flags2=['--assert --error-limit 1000'], - fails=True) +test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) test.passes() diff --git a/test_regress/t/t_covergroup_member_event_unsup.v b/test_regress/t/t_covergroup_member_event_unsup.v new file mode 100644 index 000000000..c44eb5ba9 --- /dev/null +++ b/test_regress/t/t_covergroup_member_event_unsup.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2024 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t(input clk); + class Packet; + int m_z; + int m_x; + covergroup cov1 @m_z; + coverpoint m_x; + endgroup + endclass + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_method_bad.out b/test_regress/t/t_covergroup_method_bad.out deleted file mode 100644 index 394936ddb..000000000 --- a/test_regress/t/t_covergroup_method_bad.out +++ /dev/null @@ -1,11 +0,0 @@ -%Error: t/t_covergroup_method_bad.v:16:10: Member 'some_unknown_method' not found in covergroup 'cg' - : ... note: In instance 't' - 16 | cov1.some_unknown_method.name = "new_cov1_name"; - | ^~~~~~~~~~~~~~~~~~~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_covergroup_method_bad.v:16:30: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 16 | cov1.some_unknown_method.name = "new_cov1_name"; - | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_method_bad.v b/test_regress/t/t_covergroup_method_bad.v deleted file mode 100644 index 77cb51f3b..000000000 --- a/test_regress/t/t_covergroup_method_bad.v +++ /dev/null @@ -1,20 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2023 Wilson Snyder -// SPDX-License-Identifier: CC0-1.0 - -module t; - // verilator lint_off COVERIGN - covergroup cg(); - endgroup - - cg cov1; - - initial begin - cov1 = new; - cov1.some_unknown_method.name = "new_cov1_name"; // <-- BAD - $finish; - end - -endmodule diff --git a/test_regress/t/t_covergroup_mixed_bins.out b/test_regress/t/t_covergroup_mixed_bins.out new file mode 100644 index 000000000..5de775389 --- /dev/null +++ b/test_regress/t/t_covergroup_mixed_bins.out @@ -0,0 +1,5 @@ +cg.opcode.arith: 1 +cg.opcode.load: 1 +cg.opcode.nop: 1 +cg.opcode.others: 1 +cg.opcode.store: 1 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_mixed_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..adad8dbdf --- /dev/null +++ b/test_regress/t/t_covergroup_mixed_bins.v @@ -0,0 +1,44 @@ +// 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*/); + 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 + + // Test multi-value list bin + opcode = 8'h02; cg_inst.sample(); // load + + opcode = 8'h05; cg_inst.sample(); // store + + // Test range bin + opcode = 8'h15; cg_inst.sample(); // arith + + opcode = 8'h80; cg_inst.sample(); // others + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_negative_ranges.out b/test_regress/t/t_covergroup_negative_ranges.out new file mode 100644 index 000000000..9307d7937 --- /dev/null +++ b/test_regress/t/t_covergroup_negative_ranges.out @@ -0,0 +1,4 @@ +cg.cp_neg.mixed: 4 +cg.cp_neg.negative: 2 +cg.cp_neg.positive: 3 +cg.cp_neg.zero: 1 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..93ac9c3d0 --- /dev/null +++ b/test_regress/t/t_covergroup_negative_ranges.py @@ -0,0 +1,18 @@ +#!/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(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..dfd32763b --- /dev/null +++ b/test_regress/t/t_covergroup_negative_ranges.v @@ -0,0 +1,54 @@ +// 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-FileCopyrightText: 2025 Wilson Snyder +// 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 + $write("*-* All Finished *-*\n"); + $finish; + 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_only_ignore_illegal.out b/test_regress/t/t_covergroup_only_ignore_illegal.out new file mode 100644 index 000000000..a99435df2 --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.out @@ -0,0 +1,2 @@ +cg.cp.ign [ignore]: 2 +cg.cp.ill [illegal]: 0 diff --git a/test_regress/t/t_covergroup_only_ignore_illegal.py b/test_regress/t/t_covergroup_only_ignore_illegal.py new file mode 100755 index 000000000..9add21d5e --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.py @@ -0,0 +1,17 @@ +# DESCRIPTION: Verilator: Verilog Test driver for Verilog simulator +# +# Copyright 2026 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-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() +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + "/covergroup_report.txt", test.golden_filename) +test.passes() diff --git a/test_regress/t/t_covergroup_only_ignore_illegal.v b/test_regress/t/t_covergroup_only_ignore_illegal.v new file mode 100644 index 000000000..60af952a1 --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test covergroup where all bins are ignore_bins or illegal_bins (no regular +// bins). This exercises the totalBins==0 path in generateCoverageMethodBody() +// which returns 100.0 immediately. +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [1:0] data; + + covergroup cg; + cp: coverpoint data { + ignore_bins ign = {0, 1}; + illegal_bins ill = {2, 3}; + // No regular bins -> totalBins == 0 -> get_coverage returns 100.0 + } + endgroup + + initial begin + automatic cg cg_inst = new; + + // Only sample values that fall in ignore_bins, never illegal_bins + data = 0; cg_inst.sample(); + data = 1; cg_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_option.py b/test_regress/t/t_covergroup_option.py index 84b274f68..0379f0dd0 100755 --- a/test_regress/t/t_covergroup_option.py +++ b/test_regress/t/t_covergroup_option.py @@ -13,6 +13,4 @@ test.scenarios('simulator') test.compile() -test.execute() - test.passes() diff --git a/test_regress/t/t_covergroup_option.v b/test_regress/t/t_covergroup_option.v index e5b9e6339..1e8775d5e 100644 --- a/test_regress/t/t_covergroup_option.v +++ b/test_regress/t/t_covergroup_option.v @@ -4,17 +4,60 @@ // SPDX-FileCopyrightText: 2023 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 +// Test option.name syntax: both declaration-time and runtime assignment compile. +// Note: option.name does not currently affect the coverage.dat hierarchy key; +// the type name is used regardless. +// Also tests option.weight, option.goal, option.per_instance, option.comment. + module t; // verilator lint_off COVERIGN + logic [3:0] data; + covergroup cg(); option.name = "decl_name"; endgroup - cg cov1; + // Test option.weight, option.goal, option.per_instance, option.comment + // Covergroup-level options (parsed but passed through constructor body) + covergroup cg2; + option.weight = 2; + option.goal = 90; + option.per_instance = 1; + option.comment = "my covergroup"; + cp: coverpoint data; + endgroup + + // Coverpoint-level options: exercises visit(AstCoverpoint*) option-type branches + // in V3LinkParse (weight/goal/per_instance/comment dispatch) + covergroup cg3; + cp: coverpoint data { + option.weight = 2; + option.goal = 90; + option.per_instance = 1; + option.comment = "cp comment"; + bins lo = {[0:7]}; + bins hi = {[8:15]}; + } + endgroup + + cg cov1; + cg2 cov2; + cg3 cov3; initial begin cov1 = new; cov1.option.name = "new_cov1_name"; + + cov2 = new; + data = 5; + cov2.sample(); + + cov3 = new; + data = 3; + cov3.sample(); + data = 10; + cov3.sample(); + $finish; end diff --git a/test_regress/t/t_covergroup_option_bad2.out b/test_regress/t/t_covergroup_option_bad2.out deleted file mode 100644 index 97f4ca97d..000000000 --- a/test_regress/t/t_covergroup_option_bad2.out +++ /dev/null @@ -1,12 +0,0 @@ -%Error: t/t_covergroup_option_bad2.v:18:10: Member 'not_an_option' not found in covergroup 'cg' - : ... note: In instance 't' - : ... Suggested alternative: 'type_option' - 18 | cov1.not_an_option.name = "new_cov1_name"; - | ^~~~~~~~~~~~~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_covergroup_option_bad2.v:18:24: Unsupported: Member call on object 'CONST '1'h0'' which is a 'BASICDTYPE 'bit'' - : ... note: In instance 't' - 18 | cov1.not_an_option.name = "new_cov1_name"; - | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_option_bad2.v b/test_regress/t/t_covergroup_option_bad2.v deleted file mode 100644 index b3f290cb1..000000000 --- a/test_regress/t/t_covergroup_option_bad2.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2023 Wilson Snyder -// SPDX-License-Identifier: CC0-1.0 - -// Verilator lint_off COVERIGN - -module t; - // verilator lint_off COVERIGN - covergroup cg(); - endgroup - - cg cov1; - - initial begin - cov1 = new; - cov1.not_an_option.name = "new_cov1_name"; // <--- Bad - $finish; - 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..38c8e5fac --- /dev/null +++ b/test_regress/t/t_covergroup_option_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_covergroup_option_unsup.v:15:7: 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_in_class_colliding.py b/test_regress/t/t_covergroup_option_unsup.py similarity index 88% rename from test_regress/t/t_covergroup_in_class_colliding.py rename to test_regress/t/t_covergroup_option_unsup.py index 10ad7f0de..ef7407f24 100755 --- a/test_regress/t/t_covergroup_in_class_colliding.py +++ b/test_regress/t/t_covergroup_option_unsup.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +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..281af40c8 --- /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_static_coverage.out b/test_regress/t/t_covergroup_static_coverage.out new file mode 100644 index 000000000..fd62c5ca3 --- /dev/null +++ b/test_regress/t/t_covergroup_static_coverage.out @@ -0,0 +1,3 @@ +cg.data.high: 1 +cg.data.low: 1 +cg.data.mid: 1 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..2ea8a58d7 --- /dev/null +++ b/test_regress/t/t_covergroup_static_coverage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 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() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..8502a7cb7 --- /dev/null +++ b/test_regress/t/t_covergroup_static_coverage.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test static get_coverage() with multiple instances. +// Type-level (static) coverage using cg::get_coverage() compiles but returns +// a placeholder value (0.0); runtime behavior is not fully correct. +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-FileCopyrightText: 2024 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +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; + + cg1 = new; + cg2 = new; + cg3 = new; + + // Sample cg1 with low bin + data = 0; + cg1.sample(); + + // Sample cg2 with mid bin + data = 2; + cg2.sample(); + + // Sample cg3 with high bin + data = 4; + cg3.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out new file mode 100644 index 000000000..b5907a18e --- /dev/null +++ b/test_regress/t/t_covergroup_trans.out @@ -0,0 +1,7 @@ +cg.cp_array.arr: 3 +cg.cp_multi_item.multi: 1 +cg.cp_trans2.trans1: 1 +cg.cp_trans2.trans2: 1 +cg.cp_trans2.trans3: 1 +cg.cp_trans3.seq_a: 1 +cg.cp_trans3.seq_b: 1 diff --git a/test_regress/t/t_covergroup_trans.py b/test_regress/t/t_covergroup_trans.py new file mode 100755 index 000000000..662328708 --- /dev/null +++ b/test_regress/t/t_covergroup_trans.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=['--coverage --Wno-COVERIGN --dumpi-tree 3 --dumpi-tree-json 3']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v new file mode 100644 index 000000000..4e87080f1 --- /dev/null +++ b/test_regress/t/t_covergroup_trans.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test transition bins: simple 2-value, 3-value sequences, array bins, +// and multi-value items in transition steps. + +module t; + logic [2:0] state; + + covergroup cg; + // Simple 2-value transitions + cp_trans2: coverpoint state { + bins trans1 = (0 => 1); + bins trans2 = (1 => 2); + bins trans3 = (2 => 3); + } + // 3-value sequence transitions + cp_trans3: coverpoint state { + bins seq_a = (0 => 1 => 2); + bins seq_b = (2 => 3 => 4); + } + // Array bins: creates a separate bin per listed transition + cp_array: coverpoint state { + bins arr[] = (0 => 1), (1 => 2), (2 => 3); + } + // Multi-value item (comma list) in transition: matches 1 or 2 in second step + cp_multi_item: coverpoint state { + bins multi = (0 => 1, 2); // second element is a two-value list + } + endgroup + + cg cg_inst = new; + + initial begin + // Drive sequence 0->1->2->3->4 which hits all bins + state = 0; cg_inst.sample(); + state = 1; cg_inst.sample(); // 0=>1: trans1, seq_a pos1, arr[0=>1], multi + state = 2; cg_inst.sample(); // 1=>2: trans2, seq_a done, arr[1=>2] + state = 3; cg_inst.sample(); // 2=>3: trans3, seq_b pos1, arr[2=>3] + state = 4; cg_inst.sample(); // 3=>4: seq_b done + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans_errors_bad.out b/test_regress/t/t_covergroup_trans_errors_bad.out new file mode 100644 index 000000000..316978e46 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_errors_bad.out @@ -0,0 +1,15 @@ +%Warning-COVERIGN: t/t_covergroup_trans_errors_bad.v:16:26: Unsupported: '[*]' in cover transition + 16 | bins t_repeat = (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_errors_bad.v:15:12: Transition requires at least two values + : ... note: In instance 't' + 15 | bins t_single = (1); + | ^~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_trans_errors_bad.v:16:12: Transition set without items + : ... note: In instance 't' + 16 | bins t_repeat = (1 [*2]); + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_extends_newfirst.py b/test_regress/t/t_covergroup_trans_errors_bad.py similarity index 88% rename from test_regress/t/t_covergroup_extends_newfirst.py rename to test_regress/t/t_covergroup_trans_errors_bad.py index 10ad7f0de..ef7407f24 100755 --- a/test_regress/t/t_covergroup_extends_newfirst.py +++ b/test_regress/t/t_covergroup_trans_errors_bad.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile() +test.lint(expect_filename=test.golden_filename, fails=True) test.passes() diff --git a/test_regress/t/t_covergroup_trans_errors_bad.v b/test_regress/t/t_covergroup_trans_errors_bad.v new file mode 100644 index 000000000..e572aa41e --- /dev/null +++ b/test_regress/t/t_covergroup_trans_errors_bad.v @@ -0,0 +1,22 @@ +// 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: invalid transition bin syntax - single value and unsupported repetition + +module t; + logic [3:0] cp_expr; + + covergroup cg; + cp1: coverpoint cp_expr { + bins t_single = (1); // Error: requires at least two values + bins t_repeat = (1 [*2]); // Error: unsupported repetition operator + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_trans_restart.out b/test_regress/t/t_covergroup_trans_restart.out new file mode 100644 index 000000000..1bd092561 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_restart.out @@ -0,0 +1 @@ +cg.cp_state.trans_restart: 1 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..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_restart.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +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..ce33d1cb8 --- /dev/null +++ b/test_regress/t/t_covergroup_trans_restart.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Test transition bins - restart behavior +// Known limitation: multi-value transition bins with restart semantics generate +// incomplete case statements; complex transitions are not fully supported. +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [2:0] state; + + 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"); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_option_bad.out b/test_regress/t/t_covergroup_undef_field_bad.out similarity index 54% rename from test_regress/t/t_covergroup_option_bad.out rename to test_regress/t/t_covergroup_undef_field_bad.out index f28297310..7899001b7 100644 --- a/test_regress/t/t_covergroup_option_bad.out +++ b/test_regress/t/t_covergroup_undef_field_bad.out @@ -1,8 +1,8 @@ -%Error: t/t_covergroup_option_bad.v:14:5: Syntax error; expected 'option' or 'type_option': 'bad_cg_non_option' +%Error: t/t_covergroup_undef_field_bad.v:14:5: Syntax error; expected 'option' or 'type_option': 'bad_cg_non_option' 14 | bad_cg_non_option.name = "xx"; | ^~~~~~~~~~~~~~~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_option_bad.v:20:7: Syntax error; expected 'option' or 'type_option': 'bad_cross_non_option' +%Error: t/t_covergroup_undef_field_bad.v:20:7: Syntax error; expected 'option' or 'type_option': 'bad_cross_non_option' 20 | bad_cross_non_option.name = "xx"; | ^~~~~~~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_method_bad.py b/test_regress/t/t_covergroup_undef_field_bad.py similarity index 100% rename from test_regress/t/t_covergroup_method_bad.py rename to test_regress/t/t_covergroup_undef_field_bad.py diff --git a/test_regress/t/t_covergroup_option_bad.v b/test_regress/t/t_covergroup_undef_field_bad.v similarity index 100% rename from test_regress/t/t_covergroup_option_bad.v rename to test_regress/t/t_covergroup_undef_field_bad.v diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index 11fd37c51..20a1a50fd 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -1,408 +1,244 @@ -%Warning-COVERIGN: t/t_covergroup_unsup.v:38:3: Ignoring unsupported: covergroup - 38 | covergroup cg_empty; - | ^~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:65:24: Unsupported: '@@' coverage event + 65 | covergroup cg_atat() @@ (begin funca or end funcb); + | ^~ ... 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:41:3: Ignoring unsupported: covergroup - 41 | covergroup cg_opt; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:60:33: Ignoring unsupported: coverage clocking event - 60 | covergroup cg_clockingevent() @(posedge clk); - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:60:3: Ignoring unsupported: covergroup - 60 | covergroup cg_clockingevent() @(posedge clk); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:62:3: Ignoring unsupported: covergroup - 62 | covergroup cg_withfunction() with function sample (a); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:64:24: Ignoring unsupported: coverage '@@' events - 64 | covergroup cg_atat() @@ (begin funca or end funcb); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:64:3: Ignoring unsupported: covergroup - 64 | covergroup cg_atat() @@ (begin funca or end funcb); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:66:3: Ignoring unsupported: covergroup - 66 | covergroup cg_bracket; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:69:3: Ignoring unsupported: covergroup - 69 | covergroup cg_bracket2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:73:5: Ignoring unsupported: coverpoint - 73 | coverpoint a; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:72:3: Ignoring unsupported: covergroup - 72 | covergroup cg_cp; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:76:18: Ignoring unsupported: cover 'iff' - 76 | coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:76:5: Ignoring unsupported: coverpoint - 76 | coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:75:3: Ignoring unsupported: covergroup - 75 | covergroup cg_cp_iff; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:79:22: Ignoring unsupported: cover 'iff' - 79 | id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:79:9: Ignoring unsupported: coverpoint - 79 | id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:78:3: Ignoring unsupported: covergroup - 78 | covergroup cg_id_cp_iff; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:82:26: Ignoring unsupported: cover 'iff' - 82 | int id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:82:13: Ignoring unsupported: coverpoint - 82 | int id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:81:3: Ignoring unsupported: covergroup - 81 | covergroup cg_id_cp_id1; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:85:30: Ignoring unsupported: cover 'iff' - 85 | var int id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:85:17: Ignoring unsupported: coverpoint - 85 | var int id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:84:3: Ignoring unsupported: covergroup - 84 | covergroup cg_id_cp_id2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:88:32: Ignoring unsupported: cover 'iff' - 88 | var [3:0] id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:88:19: Ignoring unsupported: coverpoint - 88 | var [3:0] id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:87:3: Ignoring unsupported: covergroup - 87 | covergroup cg_id_cp_id3; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:91:28: Ignoring unsupported: cover 'iff' - 91 | [3:0] id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:91:15: Ignoring unsupported: coverpoint - 91 | [3:0] id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:90:3: Ignoring unsupported: covergroup - 90 | covergroup cg_id_cp_id4; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:94:29: Ignoring unsupported: cover 'iff' - 94 | signed id: coverpoint a iff (b); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:94:16: Ignoring unsupported: coverpoint - 94 | signed id: coverpoint a iff (b); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:93:3: Ignoring unsupported: covergroup - 93 | covergroup cg_id_cp_id5; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:98:16: Ignoring unsupported: cover 'iff' - 98 | cross a, b iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:98:5: Ignoring unsupported: cover cross - 98 | cross a, b iff (!rst); - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:97:3: Ignoring unsupported: covergroup - 97 | covergroup cg_cross; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:101:16: Ignoring unsupported: cover 'iff' - 101 | cross a, b iff (!rst) {} - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:101:5: Ignoring unsupported: cover cross - 101 | cross a, b iff (!rst) {} - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:100:3: Ignoring unsupported: covergroup - 100 | covergroup cg_cross2; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:104:5: Ignoring unsupported: cover cross - 104 | cross a, b { option.comment = "cross"; option.weight = 12; } - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:103:3: Ignoring unsupported: covergroup - 103 | covergroup cg_cross3; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:108:21: Ignoring unsupported: coverage cross 'function' declaration - 108 | function void crossfunc; endfunction +%Warning-COVERIGN: t/t_covergroup_unsup.v:99:21: Unsupported: 'iff' in coverage cross + 99 | cross a, b iff (!rst); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:102:21: Unsupported: 'iff' in coverage cross + 102 | cross a, b iff (!rst) {} + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:109:21: Unsupported: 'function' in coverage cross body + 109 | function void crossfunc; endfunction | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:109:18: Ignoring unsupported: coverage select function call - 109 | bins one = crossfunc(); +%Warning-COVERIGN: t/t_covergroup_unsup.v:110:18: Unsupported: function call in coverage select expression + 110 | bins one = crossfunc(); | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:109:7: Ignoring unsupported: coverage cross bin - 109 | bins one = crossfunc(); +%Warning-COVERIGN: t/t_covergroup_unsup.v:110:7: Unsupported: explicit coverage cross bins + 110 | bins one = crossfunc(); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:107:5: Ignoring unsupported: cover cross - 107 | cross a, b { - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:106:3: Ignoring unsupported: covergroup - 106 | covergroup cg_cross4; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:113:26: Ignoring unsupported: cover 'iff' - 113 | my_cg_id: cross a, b iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:113:15: Ignoring unsupported: cover cross - 113 | my_cg_id: cross a, b iff (!rst); - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:112:3: Ignoring unsupported: covergroup - 112 | covergroup cg_cross_id; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:118:15: Ignoring unsupported: cover bin specification - 118 | { bins ba = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:119:22: Ignoring unsupported: cover 'iff' - 119 | { bins bar = {a} iff (!rst); } - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:119:16: Ignoring unsupported: cover bin specification - 119 | { bins bar = {a} iff (!rst); } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:120:24: Ignoring unsupported: cover bin specification - 120 | { illegal_bins ila = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:121:23: Ignoring unsupported: cover bin specification - 121 | { ignore_bins iga = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:123:17: Ignoring unsupported: cover bin specification - 123 | { bins ba[] = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:124:18: Ignoring unsupported: cover bin specification - 124 | { bins ba[2] = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:126:21: Ignoring unsupported: cover bin 'with' specification - 126 | { bins ba = {a} with ( b ); } +%Warning-COVERIGN: t/t_covergroup_unsup.v:114:31: Unsupported: 'iff' in coverage cross + 114 | my_cg_id: cross a, b iff (!rst); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:125:14: Unsupported: 'bins' explicit array size (treated as '[]') + 125 | { bins ba[2] = {a}; } + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:127:21: Unsupported: 'with' in cover bin (bin created without filter) + 127 | { bins ba = {a} with ( b ); } | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:128:25: Ignoring unsupported: cover bin 'wildcard' specification - 128 | { wildcard bins bwa = {a}; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:129:32: Ignoring unsupported: cover bin 'wildcard' 'with' specification - 129 | { wildcard bins bwaw = {a} with ( b ); } +%Warning-COVERIGN: t/t_covergroup_unsup.v:130:32: Unsupported: 'with' in wildcard cover bin + 130 | { wildcard bins bwaw = {a} with ( b ); } | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:131:18: Ignoring unsupported: cover bin 'default' - 131 | { bins def = default; } - | ^~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:132:27: Ignoring unsupported: cover bin 'default' 'sequence' - 132 | { bins defs = default sequence; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:133:27: Unsupported: 'sequence' in default cover bin + 133 | { bins defs = default sequence; } | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:134:16: Ignoring unsupported: cover bin trans list - 134 | { bins bts = ( 1, 2 ); } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:135:7: Ignoring unsupported: cover bin 'wildcard' trans list - 135 | { wildcard bins wbts = ( 1, 2 ); } +%Warning-COVERIGN: t/t_covergroup_unsup.v:136:7: Unsupported: 'wildcard' transition list in cover bin + 136 | { wildcard bins wbts = ( 1, 2 ); } | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:136:31: Ignoring unsupported: covergroup value range - 136 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:137:31: Unsupported: covergroup value range '[...]' + 137 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:136:42: Ignoring unsupported: covergroup value range - 136 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:137:42: Unsupported: covergroup value range '[...]' + 137 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:136:57: Ignoring unsupported: covergroup value range - 136 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:137:57: Unsupported: covergroup value range '[...]' + 137 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:136:17: Ignoring unsupported: cover bin trans list - 136 | { bins bts2 = ( 2, 3 ), ( [5:6] ), ( [5 +/- 2] ), ( [ 5 +%- 20.0] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:138:25: Ignoring unsupported: cover trans set '=>' - 138 | { bins bts2 = ( 1,5 => 6,7 ) ; } - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:138:17: Ignoring unsupported: cover bin trans list - 138 | { bins bts2 = ( 1,5 => 6,7 ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:139:23: Ignoring unsupported: cover '[*' - 139 | { bins bts2 = ( 3 [*5] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:140:23: Unsupported: '[*]' in cover transition + 140 | { bins bts2 = ( 3 [*5] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:139:17: Ignoring unsupported: cover bin trans list - 139 | { bins bts2 = ( 3 [*5] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:140:23: Ignoring unsupported: cover '[*' - 140 | { bins bts2 = ( 3 [*5:6] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:141:23: Unsupported: '[*]' in cover transition + 141 | { bins bts2 = ( 3 [*5:6] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:140:17: Ignoring unsupported: cover bin trans list - 140 | { bins bts2 = ( 3 [*5:6] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:141:23: Ignoring unsupported: cover '[->' - 141 | { bins bts2 = ( 3 [->5] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:142:23: Unsupported: '[->' in cover transition + 142 | { bins bts2 = ( 3 [->5] ) ; } | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:141:17: Ignoring unsupported: cover bin trans list - 141 | { bins bts2 = ( 3 [->5] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:142:23: Ignoring unsupported: cover '[->' - 142 | { bins bts2 = ( 3 [->5:6] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:143:23: Unsupported: '[->' in cover transition + 143 | { bins bts2 = ( 3 [->5:6] ) ; } | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:142:17: Ignoring unsupported: cover bin trans list - 142 | { bins bts2 = ( 3 [->5:6] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:143:23: Ignoring unsupported: cover '[=' - 143 | { bins bts2 = ( 3 [=5] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:144:23: Unsupported: '[=]' in cover transition + 144 | { bins bts2 = ( 3 [=5] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:143:17: Ignoring unsupported: cover bin trans list - 143 | { bins bts2 = ( 3 [=5] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:144:23: Ignoring unsupported: cover '[=' - 144 | { bins bts2 = ( 3 [=5:6] ) ; } +%Warning-COVERIGN: t/t_covergroup_unsup.v:145:23: Unsupported: '[=]' in cover transition + 145 | { bins bts2 = ( 3 [=5:6] ) ; } | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:144:17: Ignoring unsupported: cover bin trans list - 144 | { bins bts2 = ( 3 [=5:6] ) ; } - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:116:3: Ignoring unsupported: covergroup - 116 | covergroup cg_binsoroptions_bk1; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:149:24: Ignoring unsupported: cover bin 'with' specification - 149 | bins div_by_2 = a with (item % 2 == 0); +%Warning-COVERIGN: t/t_covergroup_unsup.v:151:12: Unsupported: 'bins' array (non-auto) + 151 | { bins nonAuto[4]; } + | ^~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:153:35: Unsupported: 'with' in cover bin (bin created without filter) + 153 | { ignore_bins ib_with = {1,2} with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:154:37: Unsupported: 'with' in cover bin (bin created without filter) + 154 | { illegal_bins lib_with = {1,2} with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:156:29: Unsupported: 'with' in cover bin + 156 | { ignore_bins ib_cp = a with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:157:31: Unsupported: 'with' in cover bin + 157 | { illegal_bins lib_cp = a with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:159:45: Unsupported: 'with' in wildcard cover bin + 159 | { wildcard ignore_bins wib_with = {1,2} with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:160:47: Unsupported: 'with' in wildcard cover bin + 160 | { wildcard illegal_bins wlib_with = {1,2} with ( b ); } + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:162:7: Unsupported: 'wildcard' transition list in cover bin + 162 | { wildcard ignore_bins wib_trans = ( 1 => 2 ); } + | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:163:7: Unsupported: 'wildcard' transition list in cover bin + 163 | { wildcard illegal_bins wlib_trans = ( 1 => 2 ); } + | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:165:40: Unsupported: 'sequence' in default cover bin + 165 | { ignore_bins ib_def_seq = default sequence; } + | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:166:42: Unsupported: 'sequence' in default cover bin + 166 | { illegal_bins lib_def_seq = default sequence; } + | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:171:24: Unsupported: 'with' in cover bin + 171 | bins div_by_2 = a with (item % 2 == 0); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:150:32: Ignoring unsupported: cover bin 'with' specification - 150 | bins div_by_2_paren[] = a with (item % 2 == 0); +%Warning-COVERIGN: t/t_covergroup_unsup.v:172:32: Unsupported: 'with' in cover bin + 172 | bins div_by_2_paren[] = a with (item % 2 == 0); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:148:5: Ignoring unsupported: coverpoint - 148 | coverpoint a { - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:147:3: Ignoring unsupported: covergroup - 147 | covergroup cg_coverpoint_ref; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:156:20: Ignoring unsupported: coverage select expression 'binsof' - 156 | bins bin_a = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:178:20: Unsupported: 'binsof' in coverage select expression + 178 | bins bin_a = binsof(a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:156:7: Ignoring unsupported: coverage cross bin - 156 | bins bin_a = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:178:7: Unsupported: explicit coverage cross bins + 178 | bins bin_a = binsof(a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:157:21: Ignoring unsupported: coverage select expression 'binsof' - 157 | bins bin_ai = binsof(a) iff (!rst); +%Warning-COVERIGN: t/t_covergroup_unsup.v:179:21: Unsupported: 'binsof' in coverage select expression + 179 | bins bin_ai = binsof(a) iff (!rst); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:157:31: Ignoring unsupported: cover 'iff' - 157 | bins bin_ai = binsof(a) iff (!rst); - | ^~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:157:7: Ignoring unsupported: coverage cross bin - 157 | bins bin_ai = binsof(a) iff (!rst); +%Warning-COVERIGN: t/t_covergroup_unsup.v:179:7: Unsupported: explicit coverage cross bins + 179 | bins bin_ai = binsof(a) iff (!rst); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:158:20: Ignoring unsupported: coverage select expression 'binsof' - 158 | bins bin_c = binsof(cp.x); +%Warning-COVERIGN: t/t_covergroup_unsup.v:180:20: Unsupported: 'binsof' in coverage select expression + 180 | bins bin_c = binsof(cp.x); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:158:7: Ignoring unsupported: coverage cross bin - 158 | bins bin_c = binsof(cp.x); +%Warning-COVERIGN: t/t_covergroup_unsup.v:180:7: Unsupported: explicit coverage cross bins + 180 | bins bin_c = binsof(cp.x); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:159:21: Ignoring unsupported: coverage select expression 'binsof' - 159 | bins bin_na = ! binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:181:21: Unsupported: 'binsof' in coverage select expression + 181 | bins bin_na = ! binsof(a); | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:159:7: Ignoring unsupported: coverage cross bin - 159 | bins bin_na = ! binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:181:7: Unsupported: explicit coverage cross bins + 181 | bins bin_na = ! binsof(a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:161:30: Ignoring unsupported: coverage select expression 'intersect' - 161 | bins bin_d = binsof(a) intersect { b }; +%Warning-COVERIGN: t/t_covergroup_unsup.v:183:30: Unsupported: 'intersect' in coverage select expression + 183 | bins bin_d = binsof(a) intersect { b }; | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:161:7: Ignoring unsupported: coverage cross bin - 161 | bins bin_d = binsof(a) intersect { b }; +%Warning-COVERIGN: t/t_covergroup_unsup.v:183:7: Unsupported: explicit coverage cross bins + 183 | bins bin_d = binsof(a) intersect { b }; | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:162:31: Ignoring unsupported: coverage select expression 'intersect' - 162 | bins bin_nd = ! binsof(a) intersect { b }; +%Warning-COVERIGN: t/t_covergroup_unsup.v:184:31: Unsupported: 'intersect' in coverage select expression + 184 | bins bin_nd = ! binsof(a) intersect { b }; | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:162:7: Ignoring unsupported: coverage cross bin - 162 | bins bin_nd = ! binsof(a) intersect { b }; +%Warning-COVERIGN: t/t_covergroup_unsup.v:184:7: Unsupported: explicit coverage cross bins + 184 | bins bin_nd = ! binsof(a) intersect { b }; | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:164:20: Ignoring unsupported: coverage select expression with - 164 | bins bin_e = with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:186:20: Unsupported: 'with' in coverage select expression + 186 | bins bin_e = with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:164:7: Ignoring unsupported: coverage cross bin - 164 | bins bin_e = with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:186:7: Unsupported: explicit coverage cross bins + 186 | bins bin_e = with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:165:24: Ignoring unsupported: coverage select expression with - 165 | bins bin_not_e = ! with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:187:24: Unsupported: 'with' in coverage select expression + 187 | bins bin_not_e = ! with (a); | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:165:7: Ignoring unsupported: coverage cross bin - 165 | bins bin_not_e = ! with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:187:7: Unsupported: explicit coverage cross bins + 187 | bins bin_not_e = ! with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:167:23: Ignoring unsupported: coverage select expression 'binsof' - 167 | bins bin_par = (binsof(a)); +%Warning-COVERIGN: t/t_covergroup_unsup.v:189:23: Unsupported: 'binsof' in coverage select expression + 189 | bins bin_par = (binsof(a)); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:167:7: Ignoring unsupported: coverage cross bin - 167 | bins bin_par = (binsof(a)); +%Warning-COVERIGN: t/t_covergroup_unsup.v:189:7: Unsupported: explicit coverage cross bins + 189 | bins bin_par = (binsof(a)); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:22: Ignoring unsupported: coverage select expression 'binsof' - 168 | bins bin_and = binsof(a) && binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:190:22: Unsupported: 'binsof' in coverage select expression + 190 | bins bin_and = binsof(a) && binsof(b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:35: Ignoring unsupported: coverage select expression 'binsof' - 168 | bins bin_and = binsof(a) && binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:190:35: Unsupported: 'binsof' in coverage select expression + 190 | bins bin_and = binsof(a) && binsof(b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:32: Ignoring unsupported: coverage select expression '&&' - 168 | bins bin_and = binsof(a) && binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:190:32: Unsupported: '&&' in coverage select expression + 190 | bins bin_and = binsof(a) && binsof(b); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:7: Ignoring unsupported: coverage cross bin - 168 | bins bin_and = binsof(a) && binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:190:7: Unsupported: explicit coverage cross bins + 190 | bins bin_and = binsof(a) && binsof(b); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:21: Ignoring unsupported: coverage select expression 'binsof' - 169 | bins bin_or = binsof(a) || binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:191:21: Unsupported: 'binsof' in coverage select expression + 191 | bins bin_or = binsof(a) || binsof(b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:34: Ignoring unsupported: coverage select expression 'binsof' - 169 | bins bin_or = binsof(a) || binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:191:34: Unsupported: 'binsof' in coverage select expression + 191 | bins bin_or = binsof(a) || binsof(b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:31: Ignoring unsupported: coverage select expression '||' - 169 | bins bin_or = binsof(a) || binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:191:31: Unsupported: '||' in coverage select expression + 191 | bins bin_or = binsof(a) || binsof(b); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:7: Ignoring unsupported: coverage cross bin - 169 | bins bin_or = binsof(a) || binsof(b); +%Warning-COVERIGN: t/t_covergroup_unsup.v:191:7: Unsupported: explicit coverage cross bins + 191 | bins bin_or = binsof(a) || binsof(b); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:23: Ignoring unsupported: coverage select expression 'binsof' - 170 | bins bin_with = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:192:23: Unsupported: 'binsof' in coverage select expression + 192 | bins bin_with = binsof(a) with (a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:33: Ignoring unsupported: coverage select expression with - 170 | bins bin_with = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:192:33: Unsupported: 'with' in coverage select expression + 192 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:7: Ignoring unsupported: coverage cross bin - 170 | bins bin_with = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:192:7: Unsupported: explicit coverage cross bins + 192 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:26: Ignoring unsupported: coverage select expression 'binsof' - 171 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:193:26: Unsupported: 'binsof' in coverage select expression + 193 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:39: Ignoring unsupported: coverage select expression 'binsof' - 171 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:193:39: Unsupported: 'binsof' in coverage select expression + 193 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:49: Ignoring unsupported: coverage select expression with - 171 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:193:49: Unsupported: 'with' in coverage select expression + 193 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:36: Ignoring unsupported: coverage select expression '||' - 171 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:193:36: Unsupported: '||' in coverage select expression + 193 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:7: Ignoring unsupported: coverage cross bin - 171 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:193:7: Unsupported: explicit coverage cross bins + 193 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:27: Ignoring unsupported: coverage select expression 'binsof' - 172 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:194:27: Unsupported: 'binsof' in coverage select expression + 194 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:40: Ignoring unsupported: coverage select expression 'binsof' - 172 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:194:40: Unsupported: 'binsof' in coverage select expression + 194 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:50: Ignoring unsupported: coverage select expression with - 172 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:194:50: Unsupported: 'with' in coverage select expression + 194 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:37: Ignoring unsupported: coverage select expression '&&' - 172 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:194:37: Unsupported: '&&' in coverage select expression + 194 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:7: Ignoring unsupported: coverage cross bin - 172 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:194:7: Unsupported: explicit coverage cross bins + 194 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:34: Ignoring unsupported: coverage select expression 'binsof' - 173 | bins bin_multiple_fields = binsof(p.inner_packet.field); +%Warning-COVERIGN: t/t_covergroup_unsup.v:195:34: Unsupported: 'binsof' in coverage select expression + 195 | bins bin_multiple_fields = binsof(p.inner_packet.field); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:7: Ignoring unsupported: coverage cross bin - 173 | bins bin_multiple_fields = binsof(p.inner_packet.field); +%Warning-COVERIGN: t/t_covergroup_unsup.v:195:7: Unsupported: explicit coverage cross bins + 195 | bins bin_multiple_fields = binsof(p.inner_packet.field); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:155:5: Ignoring unsupported: cover cross - 155 | cross a, b { - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:154:3: Ignoring unsupported: covergroup - 154 | covergroup cg_cross_bins; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:3: Ignoring unsupported: covergroup - 177 | covergroup cgArgs(int cg_lim); - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:184:21: Ignoring unsupported: coverage clocking event - 184 | covergroup cov1 @m_z; - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:185:7: Ignoring unsupported: coverpoint - 185 | coverpoint m_x; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:186:7: Ignoring unsupported: coverpoint - 186 | coverpoint m_y; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:184:5: Ignoring unsupported: covergroup - 184 | covergroup cov1 @m_z; - | ^~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:194:5: Ignoring unsupported: covergroup - 194 | covergroup extends cg_empty; +%Warning-COVERIGN: t/t_covergroup_unsup.v:197:30: Unsupported: 'binsof' in coverage select expression + 197 | ignore_bins ib_cross = binsof(a); + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:197:7: Unsupported: explicit coverage cross bins + 197 | ignore_bins ib_cross = binsof(a); + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:198:32: Unsupported: 'binsof' in coverage select expression + 198 | illegal_bins lib_cross = binsof(a); + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:198:7: Unsupported: explicit coverage cross bins + 198 | illegal_bins lib_cross = binsof(a); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_covergroup_unsup.v:223:5: Unsupported: covergroup inheritance (extends) + 223 | covergroup extends cg_empty; | ^~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_unsup.v b/test_regress/t/t_covergroup_unsup.v index 36e6d7aa7..2f18f4cae 100644 --- a/test_regress/t/t_covergroup_unsup.v +++ b/test_regress/t/t_covergroup_unsup.v @@ -51,6 +51,7 @@ module t ( option.comment = "option_comment"; // cg, cp, cross option.at_least = 20; // cg, cp, cross option.auto_bin_max = 10; // cg, cp + type_option.auto_bin_max = 10; // cg, cp: typeOption() == true option.cross_num_print_missing = 2; // cg, cross option.detect_overlap = 1; // cg, cp option.per_instance = 1; // cg @@ -144,6 +145,27 @@ module t ( { bins bts2 = ( 3 [=5:6] ) ; } endgroup + // Additional bins syntax for grammar coverage (all generate COVERIGN warnings) + covergroup cg_bins_ext; + // Non-auto bins array without value: bins name[N] (no = {value}) -- L7049-7051 + { bins nonAuto[4]; } + // ignore_bins/illegal_bins with 'with' filter on range list -- L7067-7073 + { ignore_bins ib_with = {1,2} with ( b ); } + { illegal_bins lib_with = {1,2} with ( b ); } + // ignore_bins/illegal_bins with 'with' filter on coverpoint ref -- L7077,L7079 + { ignore_bins ib_cp = a with ( b ); } + { illegal_bins lib_cp = a with ( b ); } + // wildcard ignore/illegal bins with 'with' filter -- L7092,L7094 + { wildcard ignore_bins wib_with = {1,2} with ( b ); } + { wildcard illegal_bins wlib_with = {1,2} with ( b ); } + // wildcard ignore/illegal bins with transition list -- L7113,L7114 + { wildcard ignore_bins wib_trans = ( 1 => 2 ); } + { wildcard illegal_bins wlib_trans = ( 1 => 2 ); } + // ignore/illegal bins = default sequence -- L7128,L7130 + { ignore_bins ib_def_seq = default sequence; } + { illegal_bins lib_def_seq = default sequence; } + endgroup + covergroup cg_coverpoint_ref; coverpoint a { bins div_by_2 = a with (item % 2 == 0); @@ -171,6 +193,9 @@ module t ( bins bin_or_with = binsof(a) || binsof(a) with (a); bins bin_and_with = binsof(a) && binsof(a) with (a); bins bin_multiple_fields = binsof(p.inner_packet.field); + // explicit cross ignore/illegal bins (unsupported) -- L7253, L7255 + ignore_bins ib_cross = binsof(a); + illegal_bins lib_cross = binsof(a); } endgroup @@ -182,18 +207,23 @@ module t ( int m_y; int m_z; covergroup cov1 @m_z; - coverpoint m_x; - coverpoint m_y; + cp_x: coverpoint m_x; + cp_y: coverpoint m_y; +`ifdef T_COVERGROUP_UNSUP_IGN + xy_cross: cross cp_x, cp_y; // exercises cross cleanup in hasUnsupportedEvent path +`endif endgroup `ifndef T_COVERGROUP_UNSUP_IGN function new(); cov1 = new; endfunction `endif endclass +`ifndef T_COVERGROUP_UNSUP_IGN class CgEmb; covergroup extends cg_empty; endgroup endclass +`endif initial begin automatic cg_empty cov1 = new; diff --git a/test_regress/t/t_covergroup_wildcard_bins.out b/test_regress/t/t_covergroup_wildcard_bins.out new file mode 100644 index 000000000..6cedb321a --- /dev/null +++ b/test_regress/t/t_covergroup_wildcard_bins.out @@ -0,0 +1,5 @@ +cg.data.high: 1 +cg.data.low: 2 +cg.data.mid_range: 1 +cg.data.pattern: 2 +cg.data.wc_point: 2 diff --git a/test_regress/t/t_covergroup_wildcard_bins.py b/test_regress/t/t_covergroup_wildcard_bins.py new file mode 100755 index 000000000..10b6f7cd5 --- /dev/null +++ b/test_regress/t/t_covergroup_wildcard_bins.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() 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..e4101b33d --- /dev/null +++ b/test_regress/t/t_covergroup_wildcard_bins.v @@ -0,0 +1,67 @@ +// 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; + 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??}; + + // Non-wildcard range bin: InsideRange [min:max] where min != max (line 1318) + bins mid_range = {[8'h40 : 8'h4F]}; + + // Wildcard bin using single-value InsideRange [5:5] (min==max, line 1312) + wildcard bins wc_point = {[8'd5 : 8'd5]}; + } + endgroup + + initial begin + cg cg_inst; + + cg_inst = new(); + + // Test low bin (upper nibble = 0000) + data = 8'b0000_0101; // Should match 'low' + cg_inst.sample(); + + // Test high bin (upper nibble = 1111) + data = 8'b1111_1010; // Should match 'high' + cg_inst.sample(); + + // Test pattern bin (10?0_11??) + data = 8'b1000_1101; // Should match 'pattern' (10[0]0_11[0]1) + cg_inst.sample(); + + // Verify another pattern match + data = 8'b1010_1111; // Should also match 'pattern' (10[1]0_11[1]1) + cg_inst.sample(); + + // Test mid_range bin: [0x40:0x4F] + data = 8'h45; // Should match 'mid_range' + cg_inst.sample(); + + // Test wc_point bin: exact value 5 + data = 8'd5; // Should match 'wc_point' + cg_inst.sample(); + + // Verify non-matching value doesn't change coverage + data = 8'b0101_0101; // Shouldn't match any bin + cg_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_with_sample_args.py b/test_regress/t/t_covergroup_with_sample_args.py deleted file mode 100755 index 10ad7f0de..000000000 --- a/test_regress/t/t_covergroup_with_sample_args.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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.compile() - -test.passes() diff --git a/test_regress/t/t_covergroup_with_sample_args.v b/test_regress/t/t_covergroup_with_sample_args.v deleted file mode 100644 index a89b6de38..000000000 --- a/test_regress/t/t_covergroup_with_sample_args.v +++ /dev/null @@ -1,17 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -module t; - covergroup cg_with_sample(int init_val) with function sample (int addr, bit is_read); - endgroup - - cg_with_sample cov1 = new(42); - - function void run(); - cov1.sample(16, 1'b1); - endfunction -endmodule diff --git a/test_regress/t/t_covergroup_with_sample_args_default.py b/test_regress/t/t_covergroup_with_sample_args_default.py deleted file mode 100755 index 10ad7f0de..000000000 --- a/test_regress/t/t_covergroup_with_sample_args_default.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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.compile() - -test.passes() diff --git a/test_regress/t/t_covergroup_with_sample_args_default.v b/test_regress/t/t_covergroup_with_sample_args_default.v deleted file mode 100644 index 16ccf987b..000000000 --- a/test_regress/t/t_covergroup_with_sample_args_default.v +++ /dev/null @@ -1,18 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -module t; - covergroup cg_with_sample(int init) with function sample (int addr, bit is_read = 1'b0); - endgroup - - cg_with_sample cov1 = new(7); - - function void run(); - cov1.sample(5); - cov1.sample(6, 1'b1); - endfunction -endmodule diff --git a/test_regress/t/t_covergroup_with_sample_args_too_many_bad.out b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.out index a42bb7bda..ea7fdcabf 100644 --- a/test_regress/t/t_covergroup_with_sample_args_too_many_bad.out +++ b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.out @@ -1,9 +1,9 @@ -%Error: t/t_covergroup_with_sample_args_too_many_bad.v:15:26: Too many arguments in call to function 'sample' +%Error: t/t_covergroup_with_sample_args_too_many_bad.v:16:26: Too many arguments in call to function 'sample' : ... note: In instance 't' - 15 | cov1.sample(5, 1'b0, 42); + 16 | cov1.sample(5, 1'b0, 42); | ^~~~~~ : ... Location of function 'sample' declaration: - 9 | covergroup cg_with_sample(int init) with function sample (int addr, bit is_read = 1'b0); - | ^~~~~~~~~~~~~~ + 10 | covergroup cg_with_sample(int init) with function sample (int addr, bit is_read = 1'b0); + | ^~~~~~~~~~ ... 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_with_sample_args_too_many_bad.py b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.py index 1d5ccb8f4..578412ac0 100755 --- a/test_regress/t/t_covergroup_with_sample_args_too_many_bad.py +++ b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.py @@ -9,8 +9,8 @@ import vltest_bootstrap -test.scenarios('linter') +test.scenarios('simulator') -test.lint(fails=True, expect_filename=test.golden_filename) +test.compile(fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_covergroup_with_sample_args_too_many_bad.v b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.v index 718188ae6..a70c0d971 100644 --- a/test_regress/t/t_covergroup_with_sample_args_too_many_bad.v +++ b/test_regress/t/t_covergroup_with_sample_args_too_many_bad.v @@ -1,7 +1,8 @@ // DESCRIPTION: Verilator: Verilog Test module // -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-FileCopyrightText: 2025 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 /* verilator lint_off COVERIGN */ diff --git a/test_regress/t/t_covergroup_with_sample_namedargs.py b/test_regress/t/t_covergroup_with_sample_namedargs.py deleted file mode 100755 index 10ad7f0de..000000000 --- a/test_regress/t/t_covergroup_with_sample_namedargs.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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.compile() - -test.passes() diff --git a/test_regress/t/t_covergroup_with_sample_namedargs.v b/test_regress/t/t_covergroup_with_sample_namedargs.v deleted file mode 100644 index 0aa2c2e4e..000000000 --- a/test_regress/t/t_covergroup_with_sample_namedargs.v +++ /dev/null @@ -1,15 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -module t; - covergroup cgN with function sample (int addr, bit is_read); - endgroup - cgN cov = new(); - function void run(); - cov.sample(.addr(11), .is_read(1'b1)); - endfunction -endmodule diff --git a/test_regress/t/t_covergroup_with_sample_zeroargs.py b/test_regress/t/t_covergroup_with_sample_zeroargs.py deleted file mode 100755 index 10ad7f0de..000000000 --- a/test_regress/t/t_covergroup_with_sample_zeroargs.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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.compile() - -test.passes() diff --git a/test_regress/t/t_covergroup_with_sample_zeroargs.v b/test_regress/t/t_covergroup_with_sample_zeroargs.v deleted file mode 100644 index 208a4e819..000000000 --- a/test_regress/t/t_covergroup_with_sample_zeroargs.v +++ /dev/null @@ -1,15 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2025 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -/* verilator lint_off COVERIGN */ -module t; - covergroup cg0 with function sample (); - endgroup - cg0 cov = new(); - function void run(); - cov.sample(); - endfunction -endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b7bea1967..5a5c3a01f 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 @@ -126,7 +128,9 @@ module Vt_debug_emitv_t; initial begin begin : unnamedblk1 int signed other; - other = /*CRESET*/; + other = + ???? // CRESET + ; begin begin : unnamedblk2 int signed i; @@ -665,11 +669,515 @@ module Vt_debug_emitv_t; end restrict (@(posedge clk) ##1 a[0] ); + logic [2:0] cg_sig; + logic [1:0] cg_sig2; + covergroup Vt_debug_emitv_cg_basic; + function new; + cp_sig: coverpoint cg_sig { + bins low = {['sh0:'sh3]}; + bins high = {['sh4:'sh6]}; + bins multi = {'sh0, 'sh1, 'sh2}; + bins dflt = default; + ignore_bins ign = {'sh7}; + illegal_bins ill = {'sh5}; + }; + cp_options: coverpoint cg_sig2 { + + ???? // COVEROPTION + 'sh2}; + endfunction + int signed __Vint; + struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } option; + struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } type_option; + function sample; + endfunction + function start; + endfunction + function stop; + endfunction + function get_coverage; + get_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function get_inst_coverage; + get_inst_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function set_inst_name; + input string name; + endfunction + endcovergroup + covergroup Vt_debug_emitv_cg_clocked; + @(posedge clk)function new; + cp_cyc: coverpoint cg_sig; + endfunction + int signed __Vint; + struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } option; + struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } type_option; + function sample; + endfunction + function start; + endfunction + function stop; + endfunction + function get_coverage; + get_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function get_inst_coverage; + get_inst_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function set_inst_name; + input string name; + endfunction + endcovergroup + covergroup Vt_debug_emitv_cg_trans; + function new; + cp_t: coverpoint cg_sig { + bins t01 = (3'h0 => 3'h1); + bins t12 = (3'h1 => 3'h2); + bins talt = (3'h2 => 3'h3), (3'h4 => 3'h5); + }; + endfunction + int signed __Vint; + struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } option; + struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } type_option; + function sample; + endfunction + function start; + endfunction + function stop; + endfunction + function get_coverage; + get_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function get_inst_coverage; + get_inst_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function set_inst_name; + input string name; + endfunction + endcovergroup + covergroup Vt_debug_emitv_cg_cross; + function new; + cp_x: coverpoint cg_sig { + bins x0 = {'sh0}; + bins x1 = {'sh1}; + }; + cp_y: coverpoint cg_sig2 { + bins y0 = {'sh0}; + bins y1 = {'sh1}; + }; + cx: cross cp_x, cp_y; + endfunction + int signed __Vint; + struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } option; + struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } type_option; + function sample; + endfunction + function start; + endfunction + function stop; + endfunction + function get_coverage; + get_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function get_inst_coverage; + get_inst_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function set_inst_name; + input string name; + endfunction + endcovergroup + Vt_debug_emitv_cg_basic cg_basic_instVt_debug_emitv_cg_basic; + cg_basic_inst = new(); + Vt_debug_emitv_cg_clocked cg_clocked_instVt_debug_emitv_cg_clocked; + cg_clocked_inst = new(); + Vt_debug_emitv_cg_trans cg_trans_instVt_debug_emitv_cg_trans; + cg_trans_inst = new(); + Vt_debug_emitv_cg_cross cg_cross_instVt_debug_emitv_cg_cross; + cg_cross_inst = new(); endmodule +package Vt_debug_emitv_std; + class Vt_debug_emitv_semaphore; + int signed m_keyCount; + function new; + input int signed keyCount; + m_keyCount = keyCount; + endfunction + task put; + input int signed keyCount; + m_keyCount = (m_keyCount + keyCount); + endtask + task get; + input int signed keyCount; + while ((m_keyCount < keyCount)) begin + begin + + ???? // WAIT + (m_keyCount >= keyCount)end + end + m_keyCount = (m_keyCount - keyCount); + endtask + function try_get; + input int signed keyCount; + begin : label3 + try_get = + ???? // CRESET + ; + if ((m_keyCount >= keyCount)) begin + begin + m_keyCount = (m_keyCount - + keyCount); + try_get = 'sh1; + disable label3; + end + end + try_get = 'sh0; + disable label3; + end + endfunction + endclass + class Vt_debug_emitv_process; + typedef enum int signed{ + FINISHED = 32'h0, + RUNNING = 32'h1, + WAITING = 32'h2, + SUSPENDED = 32'h3, + KILLED = 32'h4 + } state; + VlProcessRef m_process; + function self; + Vt_debug_emitv_process pVt_debug_emitv_process; + begin : label4 + self = + ???? // CRESET + ; + p = new(); + $c(p.m_process = vlProcess;); + self = p; + disable label4; + end + endfunction + task set_status; + input int signed s; + $c(m_process->state(s);); + endtask + function status; + begin : label5 + status = + ???? // CRESET + ; + status = ($c(m_process->state())); + disable label5; + end + endfunction + task kill; + set_status(process::KILLED); + endtask + task suspend; + $error("std::process::suspend() not supported"); + $stop; + endtask + task resume; + set_status(process::RUNNING); + endtask + task await; + + ???? // WAIT + ((status() == process::FINISHED) || (status() + == + process:: + KILLED))endtask + task killQueue; + ref Vt_debug_emitv_process processQueue[$]Vt_debug_emitv_process; + begin : unnamedblk1_1 + integer signed __Vrepeat0; + __Vrepeat0 = processQueue.size(); + while ((__Vrepeat0 > 32'h0)) begin + begin + kill(); + end + __Vrepeat0 = (__Vrepeat0 - 32'h1); + end + end + endtask + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + + ???? // SYSTEMCSECTION + function get_randstate; + string s; + begin : label6 + get_randstate = + ???? // CRESET + ; + s = string'($c(0)); + $c(s = m_process->randstate();); + get_randstate = s; + disable label6; + end + endfunction + task set_randstate; + input string s; + $c(m_process->randstate(s);); + endtask + function new; + endfunction + process::FINISHEDprocess::KILLEDprocess::RUNNINGprocess::SUSPENDEDprocess::WAITINGendclass + function randomize; + randomize = 'sh0; + endfunction + typedef struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } vl_covergroup_options_t; + typedef struct { + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + bit detect_overlap; + } vl_coverpoint_options_t; + typedef struct { + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + } vl_cross_options_t; + typedef struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } vl_covergroup_type_options_t; + typedef struct { + int signed weight; + int signed goal; + string comment; + real real_interval; + } vl_coverpoint_type_options_t; + typedef struct { + int signed weight; + int signed goal; + string comment; + } vl_cross_type_options_t; +endpackage package Vt_debug_emitv___024unit; class Vt_debug_emitv_Cls; int signed member; member = 'sh1; + covergroup Vt_debug_emitv___vlAnonCG_cg_in_class; + function new; + cp_m: coverpoint member { + bins one = {'sh1}; + bins two = {'sh2}; + }; + endfunction + int signed __Vint; + struct { + string name; + int signed weight; + int signed goal; + string comment; + int signed at_least; + int signed auto_bin_max; + int signed cross_num_print_missing; + bit cross_retain_auto_bins; + bit detect_overlap; + bit per_instance; + bit get_inst_coverage; + } option; + struct { + int signed weight; + int signed goal; + string comment; + bit strobe; + bit merge_instances; + bit distribute_first; + real real_interval; + } type_option; + function sample; + endfunction + function start; + endfunction + function stop; + endfunction + function get_coverage; + get_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function get_inst_coverage; + get_inst_coverage = + ???? // CRESET + ; + input string covered_bins; + input string total_bins; + endfunction + function set_inst_name; + input string name; + endfunction + endcovergroup + Vt_debug_emitv___vlAnonCG_cg_in_class cg_in_classVt_debug_emitv___vlAnonCG_cg_in_class; task method; if ((this != this)) begin $stop; @@ -691,18 +1199,20 @@ module Vt_debug_emitv_sub; task inc; input int signed i; output int signed o; - o = /*CRESET*/; + o = + ???? // CRESET + ; o = ({32'h1{{1'h0, i[31:1]}}} + 32'h1); endtask function f; input int signed v; - begin : label3 + begin : label7 if ((v == 'sh0)) begin f = 'sh21; - disable label3; + disable label7; end f = ({32'h1{{31'h0, v[2]}}} + 32'h1); - disable label3; + disable label7; end endfunction real r; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 9594487f1..219ed41c6 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -21,6 +21,12 @@ endpackage class Cls; int member = 1; + covergroup cg_in_class; + cp_m: coverpoint member { + bins one = {1}; + bins two = {2}; + } + endgroup function void method; if (this != this) $stop; endfunction @@ -340,6 +346,60 @@ module t (/*AUTOARG*/ end restrict property (@(posedge clk) ##1 a[0]); + + // Covergroup constructs - exercise AstCovergroup, AstCoverpoint, AstCoverBin, AstCoverCross + logic [2:0] cg_sig; + logic [1:0] cg_sig2; + + // Basic covergroup: value bins, default bin, ignore_bins, illegal_bins, options + covergroup cg_basic; + option.per_instance = 1; + option.weight = 2; + cp_sig: coverpoint cg_sig { + bins low = {[0:3]}; + bins high = {[4:6]}; + bins multi = {0, 1, 2}; // multiple values in one bins (exercises EmitV range loop) + bins dflt = default; + ignore_bins ign = {7}; + illegal_bins ill = {5}; + } + // Coverpoint with per-coverpoint option but no explicit bins + cp_options: coverpoint cg_sig2 { + option.at_least = 2; + } + endgroup + + // Covergroup with clocking event + covergroup cg_clocked @(posedge clk); + cp_cyc: coverpoint cg_sig; + endgroup + + // Covergroup with transition bins + covergroup cg_trans; + cp_t: coverpoint cg_sig { + bins t01 = (3'b000 => 3'b001); + bins t12 = (3'b001 => 3'b010); + bins talt = (3'b010 => 3'b011), (3'b100 => 3'b101); // multiple transition sets + } + endgroup + + // Covergroup with cross coverage + covergroup cg_cross; + cp_x: coverpoint cg_sig { + bins x0 = {0}; + bins x1 = {1}; + } + cp_y: coverpoint cg_sig2 { + bins y0 = {0}; + bins y1 = {1}; + } + cx: cross cp_x, cp_y; + endgroup + + cg_basic cg_basic_inst = new; + cg_clocked cg_clocked_inst = new; + cg_trans cg_trans_inst = new; + cg_cross cg_cross_inst = new; endmodule module sub(input logic clk); diff --git a/test_regress/t/t_dist_warn_coverage.py b/test_regress/t/t_dist_warn_coverage.py index ea7e24054..d9f6b4ff5 100755 --- a/test_regress/t/t_dist_warn_coverage.py +++ b/test_regress/t/t_dist_warn_coverage.py @@ -129,6 +129,7 @@ for s in [ 'Unsupported: Per-bit array instantiations', 'Unsupported: Public functions with >64 bit outputs;', 'Unsupported: Public functions with return > 64 bits wide.', + 'Unsupported: Release statement argument is too complex array select', 'Unsupported: Replication to form', 'Unsupported: Shifting of by over 32-bit number isn\'t supported.', 'Unsupported: Size-changing cast on non-basic data type', @@ -156,6 +157,7 @@ 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 ]: Suppressed[s] = True diff --git a/test_regress/t/t_dump.v b/test_regress/t/t_dump.v index d24004b1d..7c9c98652 100644 --- a/test_regress/t/t_dump.v +++ b/test_regress/t/t_dump.v @@ -151,4 +151,14 @@ module Test(/*AUTOARG*/ endcase end + logic [1:0] cg_v1; + logic [1:0] cg_v2; + covergroup cg @(posedge clk); + option.at_least = 2; + cp1: coverpoint cg_v1 { option.weight = 1; bins lo = {0}; } + cp2: coverpoint cg_v2; + cx: cross cp1, cp2; + endgroup + cg cg_inst = new; + endmodule diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out new file mode 100644 index 000000000..bdba7118c --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -0,0 +1,171 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + // Test cross coverage: 2-way, 3-way, and 4-way crosses + + module t; +%000001 logic [1:0] addr; +%000001 logic cmd; +%000001 logic mode; +%000001 logic parity; + + // 2-way cross +%000004 covergroup cg2; +%000001 cp_addr: coverpoint addr { +%000002 bins addr0 = {0}; +%000002 bins addr1 = {1}; + } +%000001 cp_cmd: coverpoint cmd { +%000002 bins read = {0}; +%000002 bins write = {1}; + } +%000001 addr_cmd: cross cp_addr, cp_cmd; + endgroup + + // 3-way cross +%000004 covergroup cg3; +%000001 cp_addr: coverpoint addr { +%000002 bins addr0 = {0}; +%000001 bins addr1 = {1}; +%000001 bins addr2 = {2}; + } +%000001 cp_cmd: coverpoint cmd { +%000002 bins read = {0}; +%000002 bins write = {1}; + } +%000001 cp_mode: coverpoint mode { +%000002 bins normal = {0}; +%000002 bins debug = {1}; + } +%000001 addr_cmd_mode: cross cp_addr, cp_cmd, cp_mode; + endgroup + + // 4-way cross +%000004 covergroup cg4; +%000001 cp_addr: coverpoint addr { +%000002 bins addr0 = {0}; +%000002 bins addr1 = {1}; + } +%000001 cp_cmd: coverpoint cmd { +%000002 bins read = {0}; +%000002 bins write = {1}; + } +%000001 cp_mode: coverpoint mode { +%000002 bins normal = {0}; +%000002 bins debug = {1}; + } +%000001 cp_parity: coverpoint parity { +%000002 bins even = {0}; +%000002 bins odd = {1}; + } +%000001 addr_cmd_mode_parity: cross cp_addr, cp_cmd, cp_mode, cp_parity; + endgroup + + // Cross with option inside body: exercises addOptionsp in visit(AstCoverCross*) +%000002 covergroup cg5; +%000001 cp_addr: coverpoint addr { +%000001 bins addr0 = {0}; +%000001 bins addr1 = {1}; + } +%000001 cp_cmd: coverpoint cmd { +%000001 bins read = {0}; +%000001 bins write = {1}; + } +%000001 addr_cmd_opt: cross cp_addr, cp_cmd { + option.weight = 2; + } + endgroup + + // 2-way cross with range bin: exercises lo!=hi path in buildBinCondition +%000004 covergroup cg_range; +%000001 cp_addr: coverpoint addr { +%000002 bins lo_range = {[0:1]}; // range bin (lo != hi) -> makeRangeCondition path +%000002 bins hi_range = {[2:3]}; + } +%000001 cp_cmd: coverpoint cmd { +%000002 bins read = {0}; +%000002 bins write = {1}; + } +%000001 addr_cmd_range: cross cp_addr, cp_cmd; + endgroup + + // Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch + // in collectBins during cross code generation (L1139) +%000005 covergroup cg_ignore; +%000001 cp_addr: coverpoint addr { +%000001 ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path +%000002 bins a0 = {0}; +%000002 bins a1 = {1}; + } +%000001 cp_cmd: coverpoint cmd { +%000003 bins read = {0}; +%000002 bins write = {1}; + } +%000001 cross_ab: cross cp_addr, cp_cmd; + endgroup + + // Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395) +%000002 covergroup cg_unnamed_cross; +%000001 cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; } +%000001 cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; } +%000001 cross cp_a, cp_c; // no label -> crossName is empty + endgroup + +%000001 cg2 cg2_inst = new; +%000001 cg_ignore cg_ignore_inst = new; +%000001 cg_range cg_range_inst = new; +%000001 cg3 cg3_inst = new; +%000001 cg4 cg4_inst = new; +%000001 cg5 cg5_inst = new; +%000001 cg_unnamed_cross cg_unnamed_cross_inst = new; + +%000001 initial begin + // Sample 2-way: hit all 4 combinations +%000001 addr = 0; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x read +%000001 addr = 1; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x write +%000001 addr = 0; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x write +%000001 addr = 1; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x read + + // Sample 3-way: hit 4 of 12 combinations +%000001 addr = 0; cmd = 0; mode = 0; cg3_inst.sample(); // addr0 x read x normal +%000001 addr = 1; cmd = 1; mode = 0; cg3_inst.sample(); // addr1 x write x normal +%000001 addr = 2; cmd = 0; mode = 1; cg3_inst.sample(); // addr2 x read x debug +%000001 addr = 0; cmd = 1; mode = 1; cg3_inst.sample(); // addr0 x write x debug + + // Sample 4-way: hit 4 of 16 combinations +%000001 addr = 0; cmd = 0; mode = 0; parity = 0; cg4_inst.sample(); +%000001 addr = 1; cmd = 1; mode = 0; parity = 1; cg4_inst.sample(); +%000001 addr = 0; cmd = 1; mode = 1; parity = 0; cg4_inst.sample(); +%000001 addr = 1; cmd = 0; mode = 1; parity = 1; cg4_inst.sample(); + + // Sample cg5 (cross with option) +%000001 addr = 0; cmd = 0; cg5_inst.sample(); +%000001 addr = 1; cmd = 1; cg5_inst.sample(); + + // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it +%000001 addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read +%000001 addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write +%000001 addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write +%000001 addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read +%000001 addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins) + + // Sample range-bin cross +%000001 addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read +%000001 addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write +%000001 addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write +%000001 addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read + + // Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross") +%000001 addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read +%000001 addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write + +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + + endmodule + diff --git a/test_regress/t/t_vlcov_covergroup.out b/test_regress/t/t_vlcov_covergroup.out new file mode 100644 index 000000000..776bd7b5f --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup.out @@ -0,0 +1,215 @@ +COVERGROUP COVERAGE REPORT +========================== + +TOTAL: 59/83 bins covered (71.08%) + (1 ignored, 0 illegal) + +------------------------------------------------------------------------------ +Covergroup Type: cg2 [t/t_covergroup_cross.v:18] + Type Coverage: 8/8 bins (100.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED addr0 2 hits + COVERED addr1 2 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 2 hits + COVERED write 2 hits + + Cross: addr_cmd + Coverage: 4/4 bins (100.00%) + Bins: + COVERED addr0_x_read 1 hits + COVERED addr0_x_write 1 hits + COVERED addr1_x_read 1 hits + COVERED addr1_x_write 1 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg3 [t/t_covergroup_cross.v:31] + Type Coverage: 11/19 bins (57.89%) + + Coverpoint: cp_addr + Coverage: 3/3 bins (100.00%) + Bins: + COVERED addr0 2 hits + COVERED addr1 1 hits + COVERED addr2 1 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 2 hits + COVERED write 2 hits + + Coverpoint: cp_mode + Coverage: 2/2 bins (100.00%) + Bins: + COVERED normal 2 hits + COVERED debug 2 hits + + Cross: addr_cmd_mode + Coverage: 4/12 bins (33.33%) + Bins: + ZERO addr0_x_read_x_debug 0 hits + COVERED addr0_x_read_x_normal 1 hits + COVERED addr0_x_write_x_debug 1 hits + ZERO addr0_x_write_x_normal 0 hits + ZERO addr1_x_read_x_debug 0 hits + ZERO addr1_x_read_x_normal 0 hits + ZERO addr1_x_write_x_debug 0 hits + COVERED addr1_x_write_x_normal 1 hits + COVERED addr2_x_read_x_debug 1 hits + ZERO addr2_x_read_x_normal 0 hits + ZERO addr2_x_write_x_debug 0 hits + ZERO addr2_x_write_x_normal 0 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg4 [t/t_covergroup_cross.v:49] + Type Coverage: 12/24 bins (50.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED addr0 2 hits + COVERED addr1 2 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 2 hits + COVERED write 2 hits + + Coverpoint: cp_mode + Coverage: 2/2 bins (100.00%) + Bins: + COVERED normal 2 hits + COVERED debug 2 hits + + Coverpoint: cp_parity + Coverage: 2/2 bins (100.00%) + Bins: + COVERED even 2 hits + COVERED odd 2 hits + + Cross: addr_cmd_mode_parity + Coverage: 4/16 bins (25.00%) + Bins: + ZERO addr0_x_read_x_debug_x_even 0 hits + ZERO addr0_x_read_x_debug_x_odd 0 hits + COVERED addr0_x_read_x_normal_x_even 1 hits + ZERO addr0_x_read_x_normal_x_odd 0 hits + COVERED addr0_x_write_x_debug_x_even 1 hits + ZERO addr0_x_write_x_debug_x_odd 0 hits + ZERO addr0_x_write_x_normal_x_even 0 hits + ZERO addr0_x_write_x_normal_x_odd 0 hits + ZERO addr1_x_read_x_debug_x_even 0 hits + COVERED addr1_x_read_x_debug_x_odd 1 hits + ZERO addr1_x_read_x_normal_x_even 0 hits + ZERO addr1_x_read_x_normal_x_odd 0 hits + ZERO addr1_x_write_x_debug_x_even 0 hits + ZERO addr1_x_write_x_debug_x_odd 0 hits + ZERO addr1_x_write_x_normal_x_even 0 hits + COVERED addr1_x_write_x_normal_x_odd 1 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg5 [t/t_covergroup_cross.v:70] + Type Coverage: 6/8 bins (75.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED addr0 1 hits + COVERED addr1 1 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 1 hits + COVERED write 1 hits + + Cross: addr_cmd_opt + Coverage: 2/4 bins (50.00%) + Bins: + COVERED addr0_x_read 1 hits + ZERO addr0_x_write 0 hits + ZERO addr1_x_read 0 hits + COVERED addr1_x_write 1 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg_ignore [t/t_covergroup_cross.v:100] + Type Coverage: 8/8 bins (100.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED a0 2 hits + COVERED a1 2 hits + IGNORE ign 1 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 3 hits + COVERED write 2 hits + + Cross: cross_ab + Coverage: 4/4 bins (100.00%) + Bins: + COVERED a0_x_read 1 hits + COVERED a0_x_write 1 hits + COVERED a1_x_read 1 hits + COVERED a1_x_write 1 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg_range [t/t_covergroup_cross.v:85] + Type Coverage: 8/8 bins (100.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED lo_range 2 hits + COVERED hi_range 2 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 2 hits + COVERED write 2 hits + + Cross: addr_cmd_range + Coverage: 4/4 bins (100.00%) + Bins: + COVERED hi_range_x_read 1 hits + COVERED hi_range_x_write 1 hits + COVERED lo_range_x_read 1 hits + COVERED lo_range_x_write 1 hits + +------------------------------------------------------------------------------ +Covergroup Type: cg_unnamed_cross [t/t_covergroup_cross.v:112] + Type Coverage: 6/8 bins (75.00%) + + Coverpoint: cp_a + Coverage: 2/2 bins (100.00%) + Bins: + COVERED a0 1 hits + COVERED a1 1 hits + + Coverpoint: cp_c + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 1 hits + COVERED write 1 hits + + Cross: __cross7 + Coverage: 2/4 bins (50.00%) + Bins: + COVERED a0_x_read 1 hits + ZERO a0_x_write 0 hits + ZERO a1_x_read 0 hits + COVERED a1_x_write 1 hits + +------------------------------------------------------------------------------ diff --git a/test_regress/t/t_vlcov_covergroup.py b/test_regress/t/t_vlcov_covergroup.py new file mode 100755 index 000000000..f85debfbe --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup.py @@ -0,0 +1,43 @@ +#!/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('vlt') + +test.top_filename = "t/t_covergroup_cross.v" + +test.compile(verilator_flags2=['--coverage']) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--covergroup", + test.obj_dir + "/coverage.dat", +], + logfile=test.obj_dir + "/covergroup.log", + tee=False, + verilator_run=True) + +test.files_identical(test.obj_dir + "/covergroup.log", test.golden_filename) + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/t_covergroup_cross.v", + "t/" + test.name + ".annotate.out") + +test.passes()