Support scoped `new` (#4199).

This commit is contained in:
Wilson Snyder 2025-07-02 19:54:57 -04:00
parent bb41c6b26b
commit 77908447e6
15 changed files with 228 additions and 68 deletions

View File

@ -14,6 +14,7 @@ Verilator 5.037 devel
**Other:**
* Support redeclaring type as non-type; major parsing change (#2412) (#6020) (#6042) (#6044).
* Support scoped `new` (#4199).
* Support constrained random for associative arrays (#5985) (#5986). [Yilou Wang]
* Support assignments to concatenations with impure RHS (#6002). [Ryszard Rozak, Antmicro Ltd.]
* Support SARIF JSON diagnostic output with `--diagnostics-sarif`. (#6017)

View File

@ -4472,14 +4472,20 @@ class AstNew final : public AstNodeFTaskRef {
// New as constructor
// Don't need the class we are extracting from, as the "fromp()"'s datatype can get us to it
bool m_isImplicit = false; // Implicitly generated from extends args
bool m_isScoped = false; // Had :: scope when parsed
public:
AstNew(FileLine* fl, AstNodeExpr* pinsp)
: ASTGEN_SUPER_New(fl, "new", pinsp) {}
AstNew(FileLine* fl, AstNodeExpr* pinsp, bool isScoped = false)
: ASTGEN_SUPER_New(fl, "new", pinsp)
, m_isScoped{isScoped} {}
ASTGEN_MEMBERS_AstNew;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
int instrCount() const override { return widthInstrs(); }
bool isImplicit() const { return m_isImplicit; }
void isImplicit(bool flag) { m_isImplicit = flag; }
bool isScoped() const { return m_isScoped; }
void isScoped(bool flag) { m_isScoped = flag; }
};
class AstTaskRef final : public AstNodeFTaskRef {
// A reference to a task

View File

@ -454,6 +454,17 @@ void AstNetlist::timeprecisionMerge(FileLine*, const VTimescale& value) {
}
}
void AstNew::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isImplicit()) str << " [IMPLICIT]";
if (isScoped()) str << " [SCOPED]";
}
void AstNew::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isImplicit);
dumpJsonBoolFunc(str, isScoped);
dumpJsonGen(str);
}
bool AstVar::isSigPublic() const {
return (m_sigPublic || (v3Global.opt.allPublic() && !isTemp() && !isGenVar()))
&& !isIfaceRef();

View File

@ -557,6 +557,33 @@ size_t V3ParseImp::tokenPipeScanTypeEq(size_t depth) {
return depth;
}
size_t V3ParseImp::tokenPipeScanEqNew(size_t depth) {
// Search around IEEE class_new to see if is expression
// Return location of following token, or input if not found
// '=' { packageClassScopeNoId } yNEW__LEX
UINFO(9, "tokenPipelineScanEqNew tok=" << yylval.token);
UASSERT(yylval.token == '=', "Start with '='");
while (true) {
const int tok = tokenPeekp(depth)->token;
if (tok == 0) { // LCOV_EXCL_BR_LINE
UINFO(9, "tokenPipeScanEqNew hit EOF; probably syntax error to come");
break; // LCOV_EXCL_LINE
} else if (tok == yNEW__LEX) {
break;
} else if (tok == yaID__LEX) {
++depth; // yaID__LEX
depth = tokenPipeScanParam(depth, false);
if (tokenPeekp(depth)->token != yP_COLONCOLON) return 0;
++depth; // yP_COLONCOLON
continue;
} else {
return 0; // Miss
}
++depth;
}
return depth;
}
int V3ParseImp::tokenPipelineId(int token) {
const V3ParseBisonYYSType* nexttokp = tokenPeekp(0); // First char after yaID
const int nexttok = nexttokp->token;
@ -585,6 +612,7 @@ void V3ParseImp::tokenPipeline() {
// If a paren, read another
if (token == '(' //
|| token == ':' //
|| token == '=' //
|| token == yCONST__LEX //
|| token == yGLOBAL__LEX //
|| token == yLOCAL__LEX //
@ -609,6 +637,10 @@ void V3ParseImp::tokenPipeline() {
} else if (nexttok == yFORK) {
token = yP_COLON__FORK;
}
} else if (token == '=') {
if (nexttokp->token == yNEW__LEX || tokenPipeScanEqNew(0)) {
token = yP_EQ__NEW;
} // else still '='
} else if (token == yCONST__LEX) {
if (nexttok == yREF) {
token = yCONST__REF;

View File

@ -308,6 +308,7 @@ private:
size_t tokenPipeScanBracket(size_t depth) VL_MT_DISABLED;
size_t tokenPipeScanParam(size_t depth, bool forInst) VL_MT_DISABLED;
size_t tokenPipeScanTypeEq(size_t depth) VL_MT_DISABLED;
size_t tokenPipeScanEqNew(size_t depth) VL_MT_DISABLED;
const V3ParseBisonYYSType* tokenPeekp(size_t depth) VL_MT_DISABLED;
void preprocDumps(std::ostream& os, bool forInputs) VL_MT_DISABLED;
};

View File

@ -4383,11 +4383,28 @@ class WidthVisitor final : public VNVisitor {
bool assign = false;
if (VN_IS(nodep->backp(), Assign)) { // assignment case
assign = true;
AstClassRefDType* const refp
= m_vup ? VN_CAST(m_vup->dtypeNullSkipRefp(), ClassRefDType) : nullptr;
AstNode* warnp = nullptr;
AstClassRefDType* refp = nullptr;
if (nodep->isScoped()) { // = ClassOrPackage::new
UASSERT_OBJ(nodep->classOrPackagep(), nodep, "Unlinked classOrPackage");
warnp = nodep->classOrPackagep();
if (AstClass* const classp = VN_CAST(warnp, Class)) {
AstClassRefDType* const adtypep
= new AstClassRefDType{nodep->fileline(), classp, nullptr};
v3Global.rootp()->typeTablep()->addTypesp(adtypep);
refp = adtypep;
}
} else { // = new
warnp = m_vup->dtypeNullp();
refp = m_vup ? VN_CAST(m_vup->dtypeNullSkipRefp(), ClassRefDType) : nullptr;
}
if (!refp) { // e.g. int a = new;
nodep->v3error("new() assignment not legal to non-class data type "
+ (m_vup->dtypeNullp() ? m_vup->dtypep()->prettyDTypeNameQ() : ""));
nodep->v3error("new() assignment not legal to non-class "
+ (VN_IS(warnp, NodeDType) ? (
"data type "s + VN_AS(warnp, NodeDType)->prettyDTypeNameQ())
: warnp ? warnp->prettyNameQ()
: ""));
nodep->dtypep(m_vup->dtypep());
return;
}

View File

@ -531,7 +531,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> ':' // See also yP_COLON__BEGIN or yP_COLON__FORK
%token<fl> ';'
%token<fl> '<'
%token<fl> '='
%token<fl> '=' // See also yP_EQ__NEW
%token<fl> '>'
%token<fl> '?'
%token<fl> '@'
@ -1024,8 +1024,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yP_SSRIGHT ">>>"
%token<fl> yP_POW "**"
%token<fl> yP_COLON__BEGIN ":-begin"
%token<fl> yP_COLON__FORK ":-fork"
%token<fl> yP_COLON__BEGIN ":-then-begin"
%token<fl> yP_COLON__FORK ":-then-fork"
%token<fl> yP_EQ__NEW "=-then-new"
%token<fl> yP_PAR__IGNORE "(-ignored" // Used when sequence_expr:expr:( is ignored
%token<fl> yP_PAR__STRENGTH "(-for-strength"
@ -2377,10 +2378,12 @@ variable_decl_assignment<varp>: // ==IEEE: variable_decl_assignment
| idSVKwd { $$ = nullptr; }
//
// // IEEE: "dynamic_array_variable_identifier '[' ']' [ '=' dynamic_array_new ]"
// // Matches above with variable_dimensionE = "[]"
| id variable_dimensionListE sigAttrListE yP_EQ__NEW dynamic_array_new
{ $$ = VARDONEA($<fl>1, *$1, $2, $3); $$->valuep($5); }
// // IEEE: "class_variable_identifier [ '=' class_new ]"
// // variable_dimensionE must be empty
// // Pushed into variable_declExpr:dynamic_array_new
| id variable_dimensionListE sigAttrListE yP_EQ__NEW class_new
{ $$ = VARDONEA($<fl>1, *$1, $2, $3); $$->valuep($5); }
;
list_of_tf_variable_identifiers<nodep>: // ==IEEE: list_of_tf_variable_identifiers
@ -3666,8 +3669,8 @@ statement_item<nodep>: // IEEE: statement_item
// // IEEE: blocking_assignment
// // 1800-2009 restricts LHS of assignment to new to not have a range
// // This is ignored to avoid conflicts
| fexprLvalue '=' class_newNoScope ';' { $$ = new AstAssign{$2, $1, $3}; }
| fexprLvalue '=' dynamic_array_new ';' { $$ = new AstAssign{$2, $1, $3}; }
| fexprLvalue yP_EQ__NEW dynamic_array_new ';' { $$ = new AstAssign{$2, $1, $3}; }
| fexprLvalue yP_EQ__NEW class_new ';' { $$ = new AstAssign{$2, $1, $3}; }
// // IEEE: inc_or_dec_expression
| finc_or_dec_expression ';' { $$ = $1; }
//
@ -3919,7 +3922,19 @@ pinc_or_dec_expression<nodeExprp>: // IEEE: inc_or_dec_expression (for property
//UNSUP BISONPRE_COPY(inc_or_dec_expression,{s/~l~/pev_/g}) // {copied}
//UNSUP ;
class_newNoScope<nodeExprp>: // IEEE: class_new but no packageClassScope (issue #4199)
class_new<nodeExprp>: // IEEE: class_new
// // See V3ParseImp::tokenPipeScanEqNew that searches for '=' ... yNEW__LEX
class_newNoScope
{ $$ = $1; }
// // Special precedence so (...) doesn't match expr
// // A scope is not legal in front of a AstNewCopy
| packageClassScopeNoId yNEW__ETC
{ $$ = AstDot::newIfPkg($<fl>2, $1, new AstNew{$2, nullptr, true}); }
| packageClassScopeNoId yNEW__PAREN '(' list_of_argumentsE ')'
{ $$ = AstDot::newIfPkg($<fl>2, $1, new AstNew{$2, $4, true}); }
;
class_newNoScope<nodeExprp>: // IEEE: class_new but no packageClassScope
// // Special precedence so (...) doesn't match expr
yNEW__ETC { $$ = new AstNew{$1, nullptr}; }
| yNEW__ETC expr { $$ = new AstNewCopy{$1, $2}; }
@ -7425,7 +7440,7 @@ class_typeExtImpOne<nodeExprp>: // part of IEEE: class_type, where we either ge
//=== Below rules assume special scoping per above
packageClassScopeNoId<nodep>: // IEEE: [package_scope] not followed by yaID
packageClassScopeNoId<nodeExprp>: // IEEE: [package_scope] not followed by yaID
packageClassScope { $$ = $1; }
;

View File

@ -1,14 +0,0 @@
%Error: t/t_class_new_scoped.v:45:21: syntax error, unexpected new, expecting IDENTIFIER-for-type
45 | b = ClsNoArg::new;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_class_new_scoped.v:50:19: syntax error, unexpected new-then-paren, expecting IDENTIFIER-for-type
50 | b = ClsArg::new(20, 1);
| ^~~
%Error: t/t_class_new_scoped.v:55:27: syntax error, unexpected new-then-paren, expecting IDENTIFIER-for-type
55 | b = ClsParam#(100)::new(33);
| ^~~
%Error: t/t_class_new_scoped.v:60:27: syntax error, unexpected new-then-paren, expecting IDENTIFIER-for-type
60 | b = ClsParam#(200)::new(44);
| ^~~
%Error: Exiting due to

View File

@ -11,6 +11,8 @@ import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=True, expect_filename=test.golden_filename)
test.compile()
test.execute()
test.passes()

View File

@ -8,59 +8,59 @@
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
class Base;
int imembera = 10;
function new(int i);
imembera = i;
endfunction
int m_ia = 10;
function new(int i);
m_ia = i;
endfunction
endclass
class ClsNoArg extends Base;
function new();
super.new(5);
endfunction : new
function new();
super.new(5);
endfunction : new
endclass
class ClsArg extends Base;
function new(int i, int j);
super.new(i + j);
endfunction
function new(int i, int j);
super.new(i + j);
endfunction
endclass
class ClsParam #(int ADD = 100) extends Base;
function new(int def = 42);
super.new(def + ADD);
endfunction
function new(int def = 42);
super.new(def + ADD);
endfunction
endclass
module t (/*AUTOARG*/);
initial begin
Base b;
ClsNoArg c1;
ClsArg c2;
ClsParam#(100) c3;
ClsParam#(200) c4;
initial begin
Base b;
ClsNoArg c1;
ClsArg c2;
ClsParam#(100) c3;
ClsParam#(200) c4;
c1 = new;
`checkd(c1.imembera, 5);
b = ClsNoArg::new;
`checkd(b.imembera, 5);
c1 = new;
`checkd(c1.m_ia, 5);
b = ClsNoArg::new;
`checkd(b.m_ia, 5);
c2 = new(20, 1);
`checkd(c2.imembera, 21);
b = ClsArg::new(20, 1);
`checkd(b.imembera, 21);
c2 = new(20, 1);
`checkd(c2.m_ia, 21);
b = ClsArg::new(20, 1);
`checkd(b.m_ia, 21);
c3 = new(33);
`checkd(c3.imembera, 133);
b = ClsParam#(100)::new(33);
`checkd(b.imembera, 133);
c3 = new(33);
`checkd(c3.m_ia, 133);
b = ClsParam#(100)::new(33);
`checkd(b.m_ia, 133);
c4 = new(44);
`checkd(c4.imembera, 244);
b = ClsParam#(200)::new(44);
`checkd(b.imembera, 244);
c4 = new(44);
`checkd(c4.m_ia, 244);
b = ClsParam#(200)::new(44);
`checkd(b.m_ia, 244);
$write("*-* All Finished *-*\n");
$finish;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,6 @@
%Error: t/t_class_new_scoped_bad.v:17:16: new() assignment not legal to non-class 'Pkg'
: ... note: In instance 't'
17 | c = Pkg::new;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/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-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,19 @@
// 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
package Pkg;
endpackage
class C;
endclass
module t;
C c;
initial begin
c = Pkg::new; // Bad
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -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, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t ();
class SuperCls;
int s = 2;
function new(int def = 3);
s = def;
endfunction
endclass
class Cls extends SuperCls;
function new(int def = 42);
s = def;
endfunction
endclass
SuperCls super_obj;
initial begin
super_obj = Cls::new;
if (super_obj.s != 42) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule