Fix untyped dtype error on sampling functions with property arguments (#8060)

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
Artur Bieniek 2026-08-12 11:16:48 +02:00 committed by GitHub
parent 0b51926fff
commit e962efca12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 218 additions and 26 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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.

View File

@ -2555,19 +2555,26 @@ public:
class AstSampled final : public AstNodeExpr {
// Verilog $sampled
// @astgen op1 := exprp : AstNode<AstNodeExpr|AstPropSpec>
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 {

View File

@ -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();

View File

@ -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

View File

@ -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");
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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