diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index a6264b46a..0edf79650 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -387,8 +387,7 @@ class AssertVisitor final : public VNVisitor { } } AstSampled* newSampledExpr(AstNodeExpr* nodep) { - AstSampled* const sampledp = new AstSampled{nodep->fileline(), nodep, nodep->dtypep()}; - return sampledp; + return new AstSampled{nodep->fileline(), nodep, nodep->dtypep(), true}; } AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) { if (!m_monitorNumVarp) { diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 677c515a3..e015923de 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -215,8 +215,7 @@ static AstConst* newTypedConstp(FileLine* const flp, const AstNodeDType* const d } static AstNodeExpr* sampled(AstNodeExpr* exprp) { - AstSampled* const sp = new AstSampled{exprp->fileline(), exprp, exprp->dtypep()}; - return sp; + return new AstSampled{exprp->fileline(), exprp, exprp->dtypep(), true}; } static string assertCtlGetCall(const char* query, VAssertType type, diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 5e6931c71..8a1b32b24 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -79,6 +79,14 @@ private: // METHODS + static void checkSamplingFuncDType(AstNodeExpr* nodep, const AstNode* exprp) { + const AstNodeDType* const dtypep = exprp->dtypep()->skipRefp(); + if (!dtypep->isIntegralOrPacked()) { + nodep->v3error("Expected numeric type, but got a " << dtypep->prettyDTypeNameQ() + << " data type"); + } + } + AstSenTree* newSenTree(AstNode* nodep, AstSenTree* useTreep = nullptr, AstNodeCoverOrAssert* cassertp = nullptr) { // Create sentree based on clocked or default clock @@ -372,7 +380,7 @@ private: // #1step means the value that is sampled is always the signal's last value // before the clock edge (IEEE 1800-2023 14.4) AstSampled* const sampledp - = new AstSampled{flp, exprp->cloneTreePure(false), exprp->dtypep()}; + = new AstSampled{flp, exprp->cloneTreePure(false), exprp->dtypep(), true}; AstAssign* const assignp = new AstAssign{flp, refp, sampledp}; m_clockingp->addNextHere(new AstAlways{ flp, VAlwaysKwd::ALWAYS, @@ -683,6 +691,7 @@ private: void visit(AstFalling* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1}; @@ -696,6 +705,7 @@ private: void visit(AstFell* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1}; @@ -712,11 +722,13 @@ private: void visit(AstFuture* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); AstSenTree* const sentreep = nodep->sentreep(); if (sentreep) VL_DO_DANGLING(pushDeletep(sentreep->unlinkFrBack()), sentreep); nodep->sentreep(newSenTree(nodep)); } void visit(AstPast* nodep) override { + checkSamplingFuncDType(nodep, nodep->exprp()); if (nodep->sentreep()) return; // Already processed iterateChildren(nodep); nodep->sentreep(newSenTree(nodep)); @@ -774,6 +786,7 @@ private: void visit(AstRising* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1}; @@ -787,6 +800,7 @@ private: void visit(AstRose* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1}; @@ -849,7 +863,8 @@ private: // Assertion condition check AstLoop* const loopp = new AstLoop{flp}; - AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack(), nullptr}; + AstSampled* const condp + = new AstSampled{flp, nodep->exprp()->unlinkFrBack(), nullptr, true}; loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}}); loopp->addStmtsp(new AstEventControl{flp, sentreep, nullptr}); @@ -916,6 +931,7 @@ private: void visit(AstStable* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); AstSenTree* sentreep = nodep->sentreep(); @@ -931,6 +947,7 @@ private: void visit(AstSteady* nodep) override { if (nodep->user1SetOnce()) return; iterateChildren(nodep); + checkSamplingFuncDType(nodep, nodep->exprp()); FileLine* const fl = nodep->fileline(); AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack(); if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1}; @@ -941,6 +958,10 @@ private: nodep->replaceWith(exprp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } + void visit(AstSampled* nodep) override { + iterateChildren(nodep); + if (!nodep->internal()) checkSamplingFuncDType(nodep, nodep->exprp()); + } // Validate repetition count: must be a non-negative elaboration-time constant. // Shared by goto [->N] and nonconsecutive [=N] repetition. @@ -1310,8 +1331,8 @@ private: // this tick, p is not required. For s_until_with, p must be true on the q tick too. AstNodeExpr* const rawLhsp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const rawRhsp = nodep->rhsp()->unlinkFrBack(); - AstSampled* const lhsp = new AstSampled{flp, rawLhsp, rawLhsp->dtypep()}; - AstSampled* const rhsp = new AstSampled{flp, rawRhsp, rawRhsp->dtypep()}; + AstSampled* const lhsp = new AstSampled{flp, rawLhsp, rawLhsp->dtypep(), true}; + AstSampled* const rhsp = new AstSampled{flp, rawRhsp, rawRhsp->dtypep(), true}; AstNodeExpr* finalCondp = rhsp->cloneTreePure(false); if (nodep->isOverlapping()) { finalCondp = new AstLogAnd{flp, lhsp->cloneTreePure(false), finalCondp}; @@ -1470,7 +1491,7 @@ private: if (!VN_AS(nodep->backp(), NodeCoverOrAssert)->immediate()) { const AstNodeDType* const propDtp = nodep->propp()->dtypep(); nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(), - propDtp->dtypep()}); + propDtp->dtypep(), true}); } // cover counts non-vacuous matches only (IEEE 1800-2023 16.15.2), so an // implication antecedent must hold; assert passes vacuously instead. diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 46d4c13f9..6fae7af60 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2555,19 +2555,26 @@ public: class AstSampled final : public AstNodeExpr { // Verilog $sampled // @astgen op1 := exprp : AstNode + bool m_internal : 1; // Internally created, not from a source $sampled public: - AstSampled(FileLine* fl, AstNode* exprp, AstNodeDType* dtypep) - : ASTGEN_SUPER_Sampled(fl) { + AstSampled(FileLine* fl, AstNode* exprp, AstNodeDType* dtypep, bool internal = false) + : ASTGEN_SUPER_Sampled(fl) + , m_internal{internal} { this->exprp(exprp); this->dtypep(dtypep); } ASTGEN_MEMBERS_AstSampled; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; string emitVerilog() override { return "$sampled(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return 0; } - bool sameNode(const AstNode* /*samep*/) const override { return true; } + bool sameNode(const AstNode* samep) const override { + return m_internal == VN_DBG_AS(samep, Sampled)->m_internal; + } + bool internal() const { return m_internal; } bool isSystemFunc() const override { return true; } }; class AstScopeName final : public AstNodeExpr { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index e6f4f72ca..249aacdd3 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3081,6 +3081,14 @@ void AstSFormatF::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, exprFormat); dumpJsonBoolFuncIf(str, optionalFormat); } +void AstSampled::dump(std::ostream& str) const { + this->AstNodeExpr::dump(str); + if (internal()) str << " [INTERNAL]"; +} +void AstSampled::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, internal); + dumpJsonGen(str); +} void AstSel::dump(std::ostream& str) const { this->AstNodeBiop::dump(str); str << " widthConst=" << this->widthConst(); diff --git a/src/V3Slice.cpp b/src/V3Slice.cpp index 0d410cec3..6b306f90b 100644 --- a/src/V3Slice.cpp +++ b/src/V3Slice.cpp @@ -210,7 +210,7 @@ class SliceVisitor final : public VNVisitor { UINFO(9, " cloneSliceSel(" << elements << "," << elemIdx << ") " << nodep); AstNodeExpr* const exprp = VN_AS(snodep->exprp(), NodeExpr); AstNodeExpr* const selp = cloneAndSel(exprp, elements, elemIdx, needPure); - return new AstSampled{nodep->fileline(), selp, nullptr}; + return new AstSampled{nodep->fileline(), selp, nullptr, snodep->internal()}; } else if (AstExprStmt* const snodep = VN_CAST(nodep, ExprStmt)) { UINFO(9, " cloneExprStmt(" << elements << "," << elemIdx << ") " << nodep); AstNodeExpr* const resultSelp diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 183b42cff..401618cbc 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1519,7 +1519,8 @@ class WidthVisitor final : public VNVisitor { void visit(AstFell* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); userIterate(nodep->sentreep(), nullptr); nodep->dtypeSetBit(); } @@ -1527,14 +1528,16 @@ class WidthVisitor final : public VNVisitor { void visit(AstFalling* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); nodep->dtypeSetBit(); } } void visit(AstFuture* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); userIterate(nodep->sentreep(), nullptr); nodep->dtypeFrom(nodep->exprp()); } @@ -1542,10 +1545,12 @@ class WidthVisitor final : public VNVisitor { void visit(AstPast* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); nodep->dtypeFrom(nodep->exprp()); if (nodep->ticksp()) { - iterateCheckSizedSelf(nodep, "Ticks", nodep->ticksp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "Ticks", nodep->ticksp(), SELF, BOTH, + /* deferUntyped */ true); V3Const::constifyParamsEdit(nodep->ticksp()); // ticksp may change const AstConst* const constp = VN_CAST(nodep->ticksp(), Const); if (!constp) { @@ -1670,14 +1675,16 @@ class WidthVisitor final : public VNVisitor { void visit(AstRising* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); nodep->dtypeSetBit(); } } void visit(AstRose* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); userIterate(nodep->sentreep(), nullptr); nodep->dtypeSetBit(); } @@ -1686,7 +1693,8 @@ class WidthVisitor final : public VNVisitor { void visit(AstSampled* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); nodep->dtypeFrom(nodep->exprp()); } } @@ -1743,7 +1751,8 @@ class WidthVisitor final : public VNVisitor { void visit(AstStable* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); userIterate(nodep->sentreep(), nullptr); nodep->dtypeSetBit(); } @@ -1751,7 +1760,8 @@ class WidthVisitor final : public VNVisitor { void visit(AstSteady* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { - iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH); + iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH, + /* deferUntyped */ true); nodep->dtypeSetBit(); } } @@ -7532,7 +7542,10 @@ class WidthVisitor final : public VNVisitor { << pinDTypep->prettyDTypeNameQ()); continue; } - if (!portp->basicp() || portp->basicp()->isOpaque()) { + // An untyped formal preserves the actual argument's self-determined type. + if (portp->basicp() && portp->basicp()->untyped()) { + iterateCheckSelf(nodep, "Function Argument", pinp, SELF, FINAL); + } else if (!portp->basicp() || portp->basicp()->isOpaque()) { // Output args: at return caller = callee, reverse direction. checkClassAssign(nodep, "Function Argument", pinp, portDTypep, portp->direction() == VDirection::OUTPUT); @@ -9081,7 +9094,7 @@ class WidthVisitor final : public VNVisitor { (void)underp; // cppcheck } void iterateCheckSizedSelf(AstNode* parentp, const char* side, AstNode* underp, Determ determ, - Stage stage) { + Stage stage, bool deferUntyped = false) { // Coerce child to any sized-number data type; child is self-determined // i.e. isolated from expected type. // e.g. parentp=CONCAT, underp=lhs in CONCAT(lhs,rhs) @@ -9095,7 +9108,10 @@ class WidthVisitor final : public VNVisitor { AstNodeDType* const expDTypep = underp->dtypep(); underp = iterateCheck(parentp, side, underp, SELF, FINAL, expDTypep, EXTEND_EXP); AstNodeDType* const checkDtp = expDTypep->skipRefToEnump(); - if (!checkDtp->isIntegralOrPacked()) { + // Sampling functions may still reference an untyped property formal here. + // V3AssertPre checks the actual type after property argument substitution. + if (!checkDtp->isIntegralOrPacked() + && !(deferUntyped && checkDtp->basicp() && checkDtp->basicp()->untyped())) { parentp->v3error("Expected numeric type, but got a " << checkDtp->prettyDTypeNameQ() << " data type"); } diff --git a/test_regress/t/t_property_untyped.v b/test_regress/t/t_property_untyped.v index b74f88cb4..acda3b6bf 100644 --- a/test_regress/t/t_property_untyped.v +++ b/test_regress/t/t_property_untyped.v @@ -8,6 +8,9 @@ module t ( input clk ); + global clocking @(posedge clk); + endclocking + int cyc = 0; logic [4:0] val = 0; @@ -20,6 +23,27 @@ module t ( @(posedge clk) cyc % 2 == cyc_mod_2 |=> val == expected; endproperty + property sampled_values(signal); + @(posedge clk) ($changed(signal) == $changed(cyc)) + && ($changed_gclk(signal) == $changed_gclk(cyc)) + && ($changing_gclk(signal) == $changing_gclk(cyc)) + && ($falling_gclk(signal) == $falling_gclk(cyc)) + && ($fell(signal) == $fell(cyc)) + && ($fell_gclk(signal) == $fell_gclk(cyc)) + && ($future_gclk(signal) == $future_gclk(cyc)) + && ($past(signal) == $past(cyc)) + && ($past_gclk(signal) == $past_gclk(cyc)) + && ($rising_gclk(signal) == $rising_gclk(cyc)) + && ($rose(signal) == $rose(cyc)) + && ($rose_gclk(signal) == $rose_gclk(cyc)) + && ($sampled(signal) == $sampled(cyc)) + && ($stable(signal) == $stable(cyc)) + && ($stable_gclk(signal) == $stable_gclk(cyc)) + && ($steady_gclk(signal) == $steady_gclk(cyc)); + endproperty + + assert property (sampled_values(cyc)); + assert property (check(0, 5'b11111)) else begin // Assertion should pass diff --git a/test_regress/t/t_property_untyped_bad.out b/test_regress/t/t_property_untyped_bad.out new file mode 100644 index 000000000..da191450a --- /dev/null +++ b/test_regress/t/t_property_untyped_bad.out @@ -0,0 +1,66 @@ +%Error: t/t_property_untyped_bad.v:17:20: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 17 | @(posedge clk) $changed(signal) + | ^~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_property_untyped_bad.v:18:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 18 | && $changed_gclk(signal) + | ^~~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:19:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 19 | && $changing_gclk(signal) + | ^~~~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:20:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 20 | && $falling_gclk(signal) + | ^~~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:21:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 21 | && $fell(signal) + | ^~~~~ +%Error: t/t_property_untyped_bad.v:22:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 22 | && $fell_gclk(signal) + | ^~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:23:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 23 | && $future_gclk(signal) + | ^~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:24:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 24 | && $past(signal) + | ^~~~~ +%Error: t/t_property_untyped_bad.v:25:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 25 | && $past_gclk(signal) + | ^~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:26:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 26 | && $rising_gclk(signal) + | ^~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:27:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 27 | && $rose(signal) + | ^~~~~ +%Error: t/t_property_untyped_bad.v:28:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 28 | && $rose_gclk(signal) + | ^~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:29:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 29 | && $sampled(signal) + | ^~~~~~~~ +%Error: t/t_property_untyped_bad.v:30:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 30 | && $stable(signal) + | ^~~~~~~ +%Error: t/t_property_untyped_bad.v:31:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 31 | && $stable_gclk(signal) + | ^~~~~~~~~~~~ +%Error: t/t_property_untyped_bad.v:32:8: Expected numeric type, but got a 'int$[0:1]' data type + : ... note: In instance 't' + 32 | && $steady_gclk(signal); + | ^~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_property_untyped_bad.py b/test_regress/t/t_property_untyped_bad.py new file mode 100755 index 000000000..3ed57bb89 --- /dev/null +++ b/test_regress/t/t_property_untyped_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--assert']) + +test.passes() diff --git a/test_regress/t/t_property_untyped_bad.v b/test_regress/t/t_property_untyped_bad.v new file mode 100644 index 000000000..35f1b417f --- /dev/null +++ b/test_regress/t/t_property_untyped_bad.v @@ -0,0 +1,36 @@ +// 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 + +module t ( + input clk +); + + global clocking @(posedge clk); + endclocking + + int values[2]; + + property sampled_values(signal); + @(posedge clk) $changed(signal) + && $changed_gclk(signal) + && $changing_gclk(signal) + && $falling_gclk(signal) + && $fell(signal) + && $fell_gclk(signal) + && $future_gclk(signal) + && $past(signal) + && $past_gclk(signal) + && $rising_gclk(signal) + && $rose(signal) + && $rose_gclk(signal) + && $sampled(signal) + && $stable(signal) + && $stable_gclk(signal) + && $steady_gclk(signal); + endproperty + + assert property (sampled_values(values)); +endmodule