Support future sampled value functions.

This commit is contained in:
Wilson Snyder 2025-08-23 21:16:53 -04:00
parent 7affb3e580
commit ac2a75fbb5
16 changed files with 508 additions and 13 deletions

View File

@ -40,6 +40,7 @@ Verilator 5.039 devel
* Support parameter resolution of 1D unpacked array slices (#6257) (#6268). [Michael Bedford Taylor]
* Support generic interfaces (#6272). [Igor Zaworski, Antmicro Ltd.]
* Support disabling a fork from within that fork (#6314). [Ryszard Rozak, Antmicro Ltd.]
* Support future sampled value functions.
* Change control file `public_flat_*` and other signal attributes to support __ in names (#6140).
* Change runtime to exit() instead of abort(), unless under +verilated+debug.
* Change `$display("%p")` to remove space after `}`.

View File

@ -22,6 +22,98 @@
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// AssertDeFuture
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
// see IEEE 1800-2023 16.9.4.
class AssertDeFuture final : public VNVisitor {
// STATE - across all visitors
AstNodeModule* const m_modp; // Module future is underneath
const AstFuture* m_futurep; // First AstFuture found
const unsigned m_pastNum; // Prefix unique number for this module
std::map<AstVar*, AstVar*> m_delayedVars; // Old to delayed variable mapping
// STATE - for current visit position (use VL_RESTORER)
bool m_inFuture = false; // Inside a future
bool m_unsupported = false; // Printed unsupported
// METHODS
void unsupported(AstNode* nodep) {
if (m_unsupported) return;
m_unsupported = true;
nodep->v3warn(E_UNSUPPORTED,
"Unsupported/illegal: future value function used with expression with "
<< nodep->prettyOperatorName());
}
// VISITORS
void visit(AstFuture* nodep) override {
VL_RESTORER(m_inFuture);
m_inFuture = true;
iterateChildren(nodep);
// Done with the future, this subexpression is current-time
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeVarRef* nodep) override {
if (nodep->user1SetOnce()) return;
if (m_inFuture || m_unsupported)
return; // Need user1 set above, don't process when Future is removed
if (nodep->access().isWriteOrRW()) {
unsupported(nodep);
return;
}
auto it = m_delayedVars.find(nodep->varp());
AstVar* outvarp;
if (it == m_delayedVars.end()) {
AstSenTree* const sentreep = m_futurep->sentreep();
AstAlways* const alwaysp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS,
sentreep->cloneTree(false), nullptr};
m_modp->addStmtsp(alwaysp);
outvarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP,
"__Vnotfuture" + cvtToStr(m_pastNum) + "_" + nodep->name(),
nodep->dtypep()};
m_modp->addStmtsp(outvarp);
AstVarRef* varRefAWritep = new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE};
varRefAWritep->user1(true);
AstNodeVarRef* varRefAReadp = nodep->cloneTree(false);
varRefAReadp->user1(true);
AstNode* const assp = new AstAssignDly{nodep->fileline(), varRefAWritep, varRefAReadp};
alwaysp->addStmtsp(assp);
m_delayedVars.emplace(nodep->varp(), outvarp);
} else {
outvarp = it->second;
}
AstVarRef* newp = new AstVarRef{nodep->fileline(), outvarp, VAccess::READ};
newp->user1(true);
UINFO(9, "DeFuture " << nodep << " becomes " << newp);
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeFTaskRef* nodep) override { unsupported(nodep); }
void visit(AstMethodCall* nodep) override { unsupported(nodep); }
void visit(AstNode* nodep) override {
if (!nodep->isPure()) unsupported(nodep);
iterateChildren(nodep);
}
public:
// CONSTRUCTORS
explicit AssertDeFuture(AstNode* nodep, AstNodeModule* modp, unsigned pastNum)
: m_modp{modp}
, m_pastNum{pastNum} {
// See if any Future before we process
if (nodep->forall([&](const AstFuture* futurep) -> bool {
m_futurep = futurep;
return false;
}))
return;
// UINFOTREE(9, nodep, "", "defuture-in");
visit(nodep); // Nodep may get deleted
// UINFOTREE(9, nodep, "", "defuture-ou");
}
~AssertDeFuture() = default;
};
//######################################################################
// AssertVisitor
@ -32,7 +124,7 @@ class AssertVisitor final : public VNVisitor {
// NODE STATE/TYPES
// Cleared on netlist
// AstNode::user() -> bool. True if processed
// AstNode::user1() -> bool. True if processed
const VNUser1InUse m_inuser1;
// STATE
@ -203,9 +295,12 @@ class AssertVisitor final : public VNVisitor {
return bodysp;
}
void newPslAssertion(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
if (m_beginp && nodep->name() == "") nodep->name(m_beginp->name());
{ AssertDeFuture{nodep->propp(), m_modp, m_modPastNum++}; }
iterateChildren(nodep);
AstNodeExpr* const propp = VN_AS(nodep->propp()->unlinkFrBackWithNext(), NodeExpr);
AstSenTree* const sentreep = nodep->sentreep();
const string& message = nodep->name();
@ -449,6 +544,13 @@ class AssertVisitor final : public VNVisitor {
}
}
void visit(AstFuture* nodep) override {
nodep->v3error("Future sampled value function called outside property or sequence "
"expression (IEEE 16.9.4)");
nodep->replaceWith(new AstConst{nodep->fileline(), 0});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
//========== Past
void visit(AstPast* nodep) override {
iterateChildren(nodep);
@ -599,9 +701,8 @@ class AssertVisitor final : public VNVisitor {
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssert* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nodep->failsp());
void visit(AstAssert* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstAssertCtl* nodep) override {
if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) {
@ -678,13 +779,11 @@ class AssertVisitor final : public VNVisitor {
}
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssertIntrinsic* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nodep->failsp());
void visit(AstAssertIntrinsic* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstCover* nodep) override {
iterateChildren(nodep);
newPslAssertion(nodep, nullptr);
void visit(AstCover* nodep) override { //
visitAssertionIterate(nodep, nullptr);
}
void visit(AstRestrict* nodep) override {
iterateChildren(nodep);

View File

@ -453,6 +453,19 @@ private:
if (!nodep->immediate()) nodep->sentreep(newSenTree(nodep));
clearAssertInfo();
}
void visit(AstFalling* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstAnd{fl, exprp->cloneTreePure(false), new AstNot{fl, futurep}};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstFell* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
@ -469,11 +482,31 @@ private:
nodep->sentreep(newSenTree(nodep, sentreep));
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstFuture* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
AstSenTree* const sentreep = nodep->sentreep();
if (sentreep) sentreep->unlinkFrBack();
nodep->sentreep(newSenTree(nodep));
}
void visit(AstPast* nodep) override {
if (nodep->sentreep()) return; // Already processed
iterateChildren(nodep);
nodep->sentreep(newSenTree(nodep));
}
void visit(AstRising* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstAnd{fl, new AstNot{fl, exprp->cloneTreePure(false)}, futurep};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstRose* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
@ -505,6 +538,19 @@ private:
nodep->sentreep(newSenTree(nodep, sentreep));
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstSteady* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
futurep->dtypeFrom(exprp);
exprp = new AstEq{fl, exprp->cloneTreePure(false), futurep};
exprp->dtypeSetBit();
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstImplication* nodep) override {
if (nodep->sentreep()) return; // Already processed

View File

@ -1489,6 +1489,22 @@ public:
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFalling final : public AstNodeExpr {
// Verilog $falling_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstFalling(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Falling(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstFalling;
string emitVerilog() override { return "$falling_gclk(%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 widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFell final : public AstNodeExpr {
// Verilog $fell
// @astgen op1 := exprp : AstNodeExpr
@ -1507,6 +1523,24 @@ public:
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFuture final : public AstNodeExpr {
// Verilog $future_gclk
// @astgen op1 := exprp : AstNodeExpr
// @astgen op2 := sentreep : Optional[AstSenTree]
public:
AstFuture(FileLine* fl, AstNodeExpr* exprp, AstSenTree* sentreep)
: ASTGEN_SUPER_Future(fl) {
this->exprp(exprp);
this->sentreep(sentreep);
}
ASTGEN_MEMBERS_AstFuture;
string emitVerilog() override { return "$future_gclk(%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 widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstGatePin final : public AstNodeExpr {
// Possibly expand a gate primitive input pin value to match the range of the gate primitive
// @astgen op1 := exprp : AstNodeExpr // Pin expression
@ -1888,6 +1922,22 @@ public:
int instrCount() const override { return INSTR_COUNT_PLI; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstRising final : public AstNodeExpr {
// Verilog $rising_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstRising(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Rising(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstRising;
string emitVerilog() override { return "$rising_gclk(%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 widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstRose final : public AstNodeExpr {
// Verilog $rose
// @astgen op1 := exprp : AstNodeExpr
@ -2147,6 +2197,22 @@ public:
bool cleanOut() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstSteady final : public AstNodeExpr {
// Verilog $steady_gclk
// @astgen op1 := exprp : AstNodeExpr
public:
AstSteady(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_Steady(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstSteady;
string emitVerilog() override { return "$steady_gclk(%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 widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstStructSel final : public AstNodeExpr {
// Unpacked struct/union member access
// Parents: math|stmt

View File

@ -426,6 +426,11 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
iterateAndNextConstNull(nodep->exprp());
puts(")");
}
void visit(AstRising* nodep) override {
putfs(nodep, "$rising(");
iterateAndNextConstNull(nodep->exprp());
puts(")");
}
void visit(AstRose* nodep) override {
putfs(nodep, "$rose(");
iterateAndNextConstNull(nodep->exprp());
@ -444,6 +449,16 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts(")");
}
void visit(AstFalling* nodep) override {
putfs(nodep, "$falling(");
iterateAndNextConstNull(nodep->exprp());
puts(")");
}
void visit(AstFuture* nodep) override {
putfs(nodep, "$future(");
iterateAndNextConstNull(nodep->exprp());
puts(")");
}
void visit(AstStable* nodep) override {
putfs(nodep, "$stable(");
iterateAndNextConstNull(nodep->exprp());
@ -453,6 +468,11 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts(")");
}
void visit(AstSteady* nodep) override {
putfs(nodep, "$steady(");
iterateAndNextConstNull(nodep->exprp());
puts(")");
}
void visit(AstReturn* nodep) override {
putfs(nodep, "return ");
iterateAndNextConstNull(nodep->lhsp());

View File

@ -1344,6 +1344,19 @@ class WidthVisitor final : public VNVisitor {
nodep->dtypeSetBit();
}
}
void visit(AstFalling* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
nodep->dtypeSetBit();
}
}
void visit(AstFuture* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
userIterate(nodep->sentreep(), nullptr);
nodep->dtypeFrom(nodep->exprp());
}
}
void visit(AstPast* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
@ -1369,6 +1382,12 @@ class WidthVisitor final : public VNVisitor {
userIterate(nodep->sentreep(), nullptr);
}
}
void visit(AstRising* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
nodep->dtypeSetBit();
}
}
void visit(AstRose* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
@ -1431,6 +1450,12 @@ class WidthVisitor final : public VNVisitor {
nodep->dtypeSetBit();
}
}
void visit(AstSteady* nodep) override {
if (m_vup->prelim()) {
iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
nodep->dtypeSetBit();
}
}
void visit(AstStmtExpr* nodep) override {
assertAtStatement(nodep);

View File

@ -471,13 +471,16 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"$bits" { FL; return yD_BITS; }
"$changed" { FL; return yD_CHANGED; }
"$changed_gclk" { FL; return yD_CHANGED_GCLK; }
"$changing_gclk" { FL; return yD_CHANGING_GCLK; }
"$countbits" { FL; return yD_COUNTBITS; }
"$countones" { FL; return yD_COUNTONES; }
"$dimensions" { FL; return yD_DIMENSIONS; }
"$error" { FL; return yD_ERROR; }
"$falling_gclk" { FL; return yD_FALLING_GCLK; }
"$fatal" { FL; return yD_FATAL; }
"$fell" { FL; return yD_FELL; }
"$fell_gclk" { FL; return yD_FELL_GCLK; }
"$future_gclk" { FL; return yD_FUTURE_GCLK; }
"$high" { FL; return yD_HIGH; }
"$increment" { FL; return yD_INCREMENT; }
"$inferred_disable" { FL; return yD_INFERRED_DISABLE; }
@ -491,12 +494,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"$past" { FL; return yD_PAST; }
"$past_gclk" { FL; return yD_PAST_GCLK; }
"$right" { FL; return yD_RIGHT; }
"$rising_gclk" { FL; return yD_RISING_GCLK; }
"$root" { FL; return yD_ROOT; }
"$rose" { FL; return yD_ROSE; }
"$rose_gclk" { FL; return yD_ROSE_GCLK; }
"$size" { FL; return yD_SIZE; }
"$stable" { FL; return yD_STABLE; }
"$stable_gclk" { FL; return yD_STABLE_GCLK; }
"$steady_gclk" { FL; return yD_STEADY_GCLK; }
"$unpacked_dimensions" { FL; return yD_UNPACKED_DIMENSIONS; }
"$warning" { FL; return yD_WARNING; }
/* SV2005 Keywords */

View File

@ -853,6 +853,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yD_CEIL "$ceil"
%token<fl> yD_CHANGED "$changed"
%token<fl> yD_CHANGED_GCLK "$changed_gclk"
%token<fl> yD_CHANGING_GCLK "$changing_gclk"
%token<fl> yD_CLOG2 "$clog2"
%token<fl> yD_COS "$cos"
%token<fl> yD_COSH "$cosh"
@ -881,6 +882,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yD_ERROR "$error"
%token<fl> yD_EXIT "$exit"
%token<fl> yD_EXP "$exp"
%token<fl> yD_FALLING_GCLK "$falling_gclk"
%token<fl> yD_FATAL "$fatal"
%token<fl> yD_FCLOSE "$fclose"
%token<fl> yD_FDISPLAY "$fdisplay"
@ -910,6 +912,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yD_FSTROBEH "$fstrobeh"
%token<fl> yD_FSTROBEO "$fstrobeo"
%token<fl> yD_FTELL "$ftell"
%token<fl> yD_FUTURE_GCLK "$future_gclk"
%token<fl> yD_FWRITE "$fwrite"
%token<fl> yD_FWRITEB "$fwriteb"
%token<fl> yD_FWRITEH "$fwriteh"
@ -946,6 +949,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yD_REALTOBITS "$realtobits"
%token<fl> yD_REWIND "$rewind"
%token<fl> yD_RIGHT "$right"
%token<fl> yD_RISING_GCLK "$rising_gclk"
%token<fl> yD_ROOT "$root"
%token<fl> yD_ROSE "$rose"
%token<fl> yD_ROSE_GCLK "$rose_gclk"
@ -965,6 +969,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yD_STABLE "$stable"
%token<fl> yD_STABLE_GCLK "$stable_gclk"
%token<fl> yD_STACKTRACE "$stacktrace"
%token<fl> yD_STEADY_GCLK "$steady_gclk"
%token<fl> yD_STIME "$stime"
%token<fl> yD_STOP "$stop"
%token<fl> yD_STROBE "$strobe"
@ -4490,6 +4495,8 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
{ $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}}; }
| yD_CHANGED_GCLK '(' expr ')'
{ $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}}; }
| yD_CHANGING_GCLK '(' expr ')'
{ $$ = new AstLogNot{$1, new AstSteady{$1, $3}}; }
| yD_CLOG2 '(' expr ')' { $$ = new AstCLog2{$1, $3}; }
| yD_COS '(' expr ')' { $$ = new AstCosD{$1, $3}; }
| yD_COSH '(' expr ')' { $$ = new AstCoshD{$1, $3}; }
@ -4509,6 +4516,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_DIST_T '(' expr ',' expr ')' { $$ = new AstDistT{$1, $3, $5}; }
| yD_DIST_UNIFORM '(' expr ',' expr ',' expr ')' { $$ = new AstDistUniform{$1, $3, $5, $7}; }
| yD_EXP '(' expr ')' { $$ = new AstExpD{$1, $3}; }
| yD_FALLING_GCLK '(' expr ')' { $$ = new AstFalling{$1, $3}; }
| yD_FELL '(' expr ')' { $$ = new AstFell{$1, $3, nullptr}; }
| yD_FELL '(' expr ',' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; }
| yD_FELL_GCLK '(' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
@ -4523,6 +4531,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_FREAD '(' expr ',' expr ',' expr ',' expr ')' { $$ = new AstFRead{$1, $3, $5, $7, $9}; }
| yD_FREAD '(' expr ',' expr ',' ',' expr ')' { $$ = new AstFRead{$1, $3, $5, nullptr, $8}; }
| yD_FREWIND '(' expr ')' { $$ = new AstFRewind{$1, $3}; }
| yD_FUTURE_GCLK '(' expr ')' { $$ = new AstFuture{$1, $3, nullptr}; }
| yD_FLOOR '(' expr ')' { $$ = new AstFloorD{$1, $3}; }
| yD_FSCANF '(' expr ',' str commaVRDListE ')' { $$ = new AstFScanF{$1, *$5, $3, $6}; }
| yD_FSEEK '(' expr ',' expr ',' expr ')' { $$ = new AstFSeek{$1, $3, $5, $7}; }
@ -4562,6 +4571,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_REWIND '(' expr ')' { $$ = new AstFSeek{$1, $3, new AstConst{$1, 0}, new AstConst{$1, 0}}; }
| yD_RIGHT '(' exprOrDataType ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, nullptr}; }
| yD_RIGHT '(' exprOrDataType ',' expr ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, $5}; }
| yD_RISING_GCLK '(' expr ')' { $$ = new AstRising{$1, $3}; }
| yD_ROSE '(' expr ')' { $$ = new AstRose{$1, $3, nullptr}; }
| yD_ROSE '(' expr ',' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; }
| yD_ROSE_GCLK '(' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
@ -4576,11 +4586,12 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_SIZE '(' exprOrDataType ',' expr ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_SIZE, $3, $5}; }
| yD_SQRT '(' expr ')' { $$ = new AstSqrtD{$1, $3}; }
| yD_SSCANF '(' expr ',' str commaVRDListE ')' { $$ = new AstSScanF{$1, *$5, $3, $6}; }
| yD_STIME parenE
{ $$ = new AstSel{$1, new AstTime{$1, VTimescale{VTimescale::NONE}}, 0, 32}; }
| yD_STABLE '(' expr ')' { $$ = new AstStable{$1, $3, nullptr}; }
| yD_STABLE '(' expr ',' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; }
| yD_STABLE_GCLK '(' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
| yD_STEADY_GCLK '(' expr ')' { $$ = new AstSteady{$1, $3}; }
| yD_STIME parenE
{ $$ = new AstSel{$1, new AstTime{$1, VTimescale{VTimescale::NONE}}, 0, 32}; }
| yD_TAN '(' expr ')' { $$ = new AstTanD{$1, $3}; }
| yD_TANH '(' expr ')' { $$ = new AstTanhD{$1, $3}; }
| yD_TESTPLUSARGS '(' expr ')' { $$ = new AstTestPlusArgs{$1, $3}; }

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--assert'])
test.execute()
test.passes()

View File

@ -0,0 +1,87 @@
// 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-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
module t (
/*AUTOARG*/
// Inputs
clk
);
input clk;
global clocking @(posedge clk);
endclocking
logic a;
//PASSES:
int count_not_stable; // Counts changes
assert property (@(posedge clk) $stable(a))
else count_not_stable = count_not_stable + 1;
int count_not_stable_gclk; // Counts changes
assert property (@(posedge clk) $stable_gclk(a))
else count_not_stable_gclk = count_not_stable_gclk + 1;
int count_changing_gclk;
assert property (@(posedge clk) $changing_gclk(a)) count_changing_gclk = count_changing_gclk + 1;
int count_falling_gclk;
assert property (@(posedge clk) $falling_gclk(a)) count_falling_gclk = count_falling_gclk + 1;
int count_future_gclk; // Counts changes
assert property (@(posedge clk) $future_gclk(a) != a) count_future_gclk = count_future_gclk + 1;
int count_rising_gclk;
assert property (@(posedge clk) $rising_gclk(a)) count_rising_gclk = count_rising_gclk + 1;
int count_not_steady_gclk; // Counts changes
assert property (@(posedge clk) $steady_gclk(a))
else count_not_steady_gclk = count_not_steady_gclk + 1;
int cyc = 0;
always @(posedge clk) begin
`ifdef TEST_VERBOSE
$display("[%0t] cyc=%0d a=%0x gs=%x gsc=%x", $time, cyc, a, count_not_stable,
count_not_stable_gclk);
`endif
cyc <= cyc + 1;
if (cyc <= 3) begin
a <= 0;
count_not_stable = 0;
count_not_stable_gclk = 0;
end
else if (cyc == 4) begin
a <= 0; // stable
end
else if (cyc == 5) begin
a <= 1; // rise
end
else if (cyc == 6) begin
a <= 1; // stable
end
else if (cyc == 7) begin
a <= 0; // fall
end
else if (cyc == 10) begin
`checkd(count_not_stable, 2);
`checkd(count_not_stable_gclk, 2);
`checkd(count_changing_gclk, 2);
`checkd(count_falling_gclk, 1);
`checkd(count_future_gclk, 2);
`checkd(count_rising_gclk, 1);
`checkd(count_not_steady_gclk, 2);
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,22 @@
%Error: t/t_assert_future_bad.v:18:31: Future sampled value function called outside property or sequence expression (IEEE 16.9.4)
: ... note: In instance 't'
18 | else $display("Future=%0d", $future_gclk(a));
| ^~~~~~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_assert_future_bad.v:21:31: Future sampled value function called outside property or sequence expression (IEEE 16.9.4)
: ... note: In instance 't'
21 | else $display("Future=%0d", $rising_gclk(a));
| ^~~~~~~~~~~~
%Error: t/t_assert_future_bad.v:24:31: Future sampled value function called outside property or sequence expression (IEEE 16.9.4)
: ... note: In instance 't'
24 | else $display("Future=%0d", $falling_gclk(a));
| ^~~~~~~~~~~~~
%Error: t/t_assert_future_bad.v:27:31: Future sampled value function called outside property or sequence expression (IEEE 16.9.4)
: ... note: In instance 't'
27 | else $display("Future=%0d", $steady_gclk(a));
| ^~~~~~~~~~~~
%Error: t/t_assert_future_bad.v:30:31: Future sampled value function called outside property or sequence expression (IEEE 16.9.4)
: ... note: In instance 't'
30 | else $display("Future=%0d", $changing_gclk(a));
| ^~~~~~~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,34 @@
// 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-License-Identifier: CC0-1.0
module t ( /*AUTOARG*/
// Inputs
a, clk
);
input a;
input clk;
global clocking @(posedge clk);
endclocking
assert property (@(posedge clk) 1 == 1)
else $display("Future=%0d", $future_gclk(a));
assert property (@(posedge clk) 1 == 1)
else $display("Future=%0d", $rising_gclk(a));
assert property (@(posedge clk) 1 == 1)
else $display("Future=%0d", $falling_gclk(a));
assert property (@(posedge clk) 1 == 1)
else $display("Future=%0d", $steady_gclk(a));
assert property (@(posedge clk) 1 == 1)
else $display("Future=%0d", $changing_gclk(a));
initial $stop;
endmodule

View File

@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_assert_future_unsup.v:21:54: Unsupported/illegal: future value function used with expression with operator FUNCREF 'func'
: ... note: In instance 't'
21 | assert property (@(posedge clk) $future_gclk(a) == func(a));
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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-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)
test.passes()

View File

@ -0,0 +1,23 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t ( /*AUTOARG*/
// Inputs
a, clk
);
input a;
input clk;
function logic func(input logic i);
return i;
endfunction
global clocking @(posedge clk);
endclocking
assert property (@(posedge clk) $future_gclk(a) == func(a));
endmodule