From 77908447e6718dc43b84f438b7b81778300d2b61 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 2 Jul 2025 19:54:57 -0400 Subject: [PATCH] Support scoped `new` (#4199). --- Changes | 1 + src/V3AstNodeExpr.h | 10 ++- src/V3AstNodes.cpp | 11 ++++ src/V3ParseImp.cpp | 32 ++++++++++ src/V3ParseImp.h | 1 + src/V3Width.cpp | 25 ++++++-- src/verilog.y | 33 +++++++--- test_regress/t/t_class_new_scoped.out | 14 ----- test_regress/t/t_class_new_scoped.py | 4 +- test_regress/t/t_class_new_scoped.v | 76 +++++++++++------------ test_regress/t/t_class_new_scoped_bad.out | 6 ++ test_regress/t/t_class_new_scoped_bad.py | 16 +++++ test_regress/t/t_class_new_scoped_bad.v | 19 ++++++ test_regress/t/t_class_new_typed.py | 18 ++++++ test_regress/t/t_class_new_typed.v | 30 +++++++++ 15 files changed, 228 insertions(+), 68 deletions(-) delete mode 100644 test_regress/t/t_class_new_scoped.out create mode 100644 test_regress/t/t_class_new_scoped_bad.out create mode 100755 test_regress/t/t_class_new_scoped_bad.py create mode 100644 test_regress/t/t_class_new_scoped_bad.v create mode 100755 test_regress/t/t_class_new_typed.py create mode 100644 test_regress/t/t_class_new_typed.v diff --git a/Changes b/Changes index 25f4c0d95..0907c0684 100644 --- a/Changes +++ b/Changes @@ -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) diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 48bba5822..970b6e54a 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -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 diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 02c5a498e..7903046c6 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -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(); diff --git a/src/V3ParseImp.cpp b/src/V3ParseImp.cpp index 9f7ac78d6..89e1f8245 100644 --- a/src/V3ParseImp.cpp +++ b/src/V3ParseImp.cpp @@ -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; diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index f4527f194..5e210691c 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -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; }; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 3d0b01b6a..8ca414718 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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; } diff --git a/src/verilog.y b/src/verilog.y index 2149b4d3e..54475e915 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -531,7 +531,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token ':' // See also yP_COLON__BEGIN or yP_COLON__FORK %token ';' %token '<' -%token '=' +%token '=' // See also yP_EQ__NEW %token '>' %token '?' %token '@' @@ -1024,8 +1024,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yP_SSRIGHT ">>>" %token yP_POW "**" -%token yP_COLON__BEGIN ":-begin" -%token yP_COLON__FORK ":-fork" +%token yP_COLON__BEGIN ":-then-begin" +%token yP_COLON__FORK ":-then-fork" +%token yP_EQ__NEW "=-then-new" %token yP_PAR__IGNORE "(-ignored" // Used when sequence_expr:expr:( is ignored %token yP_PAR__STRENGTH "(-for-strength" @@ -2377,10 +2378,12 @@ variable_decl_assignment: // ==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($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($1, *$1, $2, $3); $$->valuep($5); } ; list_of_tf_variable_identifiers: // ==IEEE: list_of_tf_variable_identifiers @@ -3666,8 +3669,8 @@ statement_item: // 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: // IEEE: inc_or_dec_expression (for property //UNSUP BISONPRE_COPY(inc_or_dec_expression,{s/~l~/pev_/g}) // {copied} //UNSUP ; -class_newNoScope: // IEEE: class_new but no packageClassScope (issue #4199) +class_new: // 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($2, $1, new AstNew{$2, nullptr, true}); } + | packageClassScopeNoId yNEW__PAREN '(' list_of_argumentsE ')' + { $$ = AstDot::newIfPkg($2, $1, new AstNew{$2, $4, true}); } + ; + +class_newNoScope: // 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: // part of IEEE: class_type, where we either ge //=== Below rules assume special scoping per above -packageClassScopeNoId: // IEEE: [package_scope] not followed by yaID +packageClassScopeNoId: // IEEE: [package_scope] not followed by yaID packageClassScope { $$ = $1; } ; diff --git a/test_regress/t/t_class_new_scoped.out b/test_regress/t/t_class_new_scoped.out deleted file mode 100644 index 6fab6fab6..000000000 --- a/test_regress/t/t_class_new_scoped.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_class_new_scoped.py b/test_regress/t/t_class_new_scoped.py index e33e10acf..fc5a55e3f 100755 --- a/test_regress/t/t_class_new_scoped.py +++ b/test_regress/t/t_class_new_scoped.py @@ -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() diff --git a/test_regress/t/t_class_new_scoped.v b/test_regress/t/t_class_new_scoped.v index e1c5d1f65..e3b503126 100644 --- a/test_regress/t/t_class_new_scoped.v +++ b/test_regress/t/t_class_new_scoped.v @@ -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 diff --git a/test_regress/t/t_class_new_scoped_bad.out b/test_regress/t/t_class_new_scoped_bad.out new file mode 100644 index 000000000..12fc842e7 --- /dev/null +++ b/test_regress/t/t_class_new_scoped_bad.out @@ -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 diff --git a/test_regress/t/t_class_new_scoped_bad.py b/test_regress/t/t_class_new_scoped_bad.py new file mode 100755 index 000000000..31228c9a7 --- /dev/null +++ b/test_regress/t/t_class_new_scoped_bad.py @@ -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() diff --git a/test_regress/t/t_class_new_scoped_bad.v b/test_regress/t/t_class_new_scoped_bad.v new file mode 100644 index 000000000..bf9c78f59 --- /dev/null +++ b/test_regress/t/t_class_new_scoped_bad.v @@ -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 diff --git a/test_regress/t/t_class_new_typed.py b/test_regress/t/t_class_new_typed.py new file mode 100755 index 000000000..d4f986441 --- /dev/null +++ b/test_regress/t/t_class_new_typed.py @@ -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() diff --git a/test_regress/t/t_class_new_typed.v b/test_regress/t/t_class_new_typed.v new file mode 100644 index 000000000..e2a8c5bdf --- /dev/null +++ b/test_regress/t/t_class_new_typed.v @@ -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