From 4a505546a1011beb842ff1a128847eecb5d8f5f7 Mon Sep 17 00:00:00 2001 From: mjoekhan Date: Wed, 1 Jul 2026 12:45:16 +0500 Subject: [PATCH 1/4] SV: chained method calls a().b() Add parsing and elaboration for chained calls on expression results, with sv_call_chain_method1 regression. Split from steveicarus/iverilog#1330 (part 02/6). --- PExpr.cc | 25 +++- PExpr.h | 22 ++++ elab_expr.cc | 133 +++++++++++++++++++- ivtest/ivltests/sv_call_chain_method1.v | 25 ++++ ivtest/regress-vvp.list | 1 + ivtest/vvp_tests/sv_call_chain_method1.json | 9 ++ parse.y | 34 +++-- pform.cc | 18 +++ pform.h | 4 + pform_dump.cc | 4 + 10 files changed, 260 insertions(+), 15 deletions(-) create mode 100644 ivtest/ivltests/sv_call_chain_method1.v create mode 100644 ivtest/vvp_tests/sv_call_chain_method1.json diff --git a/PExpr.cc b/PExpr.cc index e9634511b..c9b35aa4d 100644 --- a/PExpr.cc +++ b/PExpr.cc @@ -271,12 +271,28 @@ PECallFunction::PECallFunction(perm_string n, const list &parms) { } +PECallFunction::PECallFunction(PExpr* chain_prefix, const pform_name_t &method, + const vector &parms) +: path_(method), parms_(parms), chain_prefix_(chain_prefix), is_overridden_(false) +{ +} + +PECallFunction::PECallFunction(PExpr* chain_prefix, const pform_name_t &method, + const list &parms) +: path_(method), parms_(parms.begin(), parms.end()), + chain_prefix_(chain_prefix), is_overridden_(false) +{ +} + PECallFunction::~PECallFunction() { + delete chain_prefix_; } void PECallFunction::declare_implicit_nets(LexicalScope*scope, NetNet::Type type) { + if (chain_prefix_) + chain_prefix_->declare_implicit_nets(scope, type); for (const auto &parm : parms_) { if (parm.parm) parm.parm->declare_implicit_nets(scope, type); @@ -285,12 +301,13 @@ void PECallFunction::declare_implicit_nets(LexicalScope*scope, NetNet::Type type bool PECallFunction::has_aa_term(Design*des, NetScope*scope) const { - bool flag = false; + if (chain_prefix_ && chain_prefix_->has_aa_term(des, scope)) + return true; for (const auto &parm : parms_) { - if (parm.parm) - flag |= parm.parm->has_aa_term(des, scope); + if (parm.parm && parm.parm->has_aa_term(des, scope)) + return true; } - return flag; + return false; } PEConcat::PEConcat(const list&p, PExpr*r) diff --git a/PExpr.h b/PExpr.h index db1f5010e..af31e0932 100644 --- a/PExpr.h +++ b/PExpr.h @@ -920,8 +920,18 @@ class PECallFunction : public PExpr { explicit PECallFunction(const pform_name_t &n, const std::list &parms); explicit PECallFunction(perm_string n, const std::list &parms); + // SystemVerilog: prefix().method(args) — prefix elaborates to a class handle. + explicit PECallFunction(PExpr* chain_prefix, const pform_name_t &method, + const std::vector &parms); + explicit PECallFunction(PExpr* chain_prefix, const pform_name_t &method, + const std::list &parms); + ~PECallFunction() override; + // For chained-call resolution (path is only the final method name). + const pform_scoped_name_t& peek_path(void) const { return path_; } + const PExpr* peek_chain_prefix(void) const { return chain_prefix_; } + virtual void dump(std::ostream &) const override; virtual void declare_implicit_nets(LexicalScope*scope, NetNet::Type type) override; @@ -940,6 +950,8 @@ class PECallFunction : public PExpr { private: pform_scoped_name_t path_; std::vector parms_; + // If non-null, this call is prefix().tail_name(...) (SV method chain). + PExpr* chain_prefix_ = nullptr; // For system functions. bool is_overridden_; @@ -985,8 +997,18 @@ class PECallFunction : public PExpr { perm_string method_name, const std::vector*src_parms) const; + NetExpr* elaborate_class_method_net_this_(Design*des, NetScope*scope, + NetExpr* this_expr, + const netclass_t*class_type, + perm_string method_name, + const std::vector*src_parms) const; + NetExpr* elaborate_expr_method_chained_(Design*des, NetScope*scope, symbol_search_results&search_results) const; + + NetExpr* elaborate_expr_chain_(Design*des, NetScope*scope, unsigned flags) const; + + unsigned test_width_chain_(Design*des, NetScope*scope, width_mode_t&mode); }; /* diff --git a/elab_expr.cc b/elab_expr.cc index 3cec07f3e..65d3da65c 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -55,6 +55,53 @@ bool type_is_vectorable(ivl_variable_type_t type) } } +/* + * For a().b()… call chains, find the class type of the prefix expression + * (the value before the final ".method(args)"). + */ +static const netclass_t* resolve_call_chain_prefix_class(Design*des, NetScope*scope, + PECallFunction* link) +{ + if (link == 0) + return 0; + + if (link->peek_chain_prefix() == 0) { + symbol_search_results sr; + if (!symbol_search(link, des, scope, link->peek_path(), UINT_MAX, &sr)) + return 0; + if (!sr.is_scope() || sr.scope->type() != NetScope::FUNC) + return 0; + NetFuncDef* fundef = sr.scope->func_def(); + ivl_assert(*link, fundef); + NetScope* dscope = fundef->scope(); + NetNet* res = dscope->find_signal(dscope->basename()); + if (res == 0) + return 0; + return dynamic_cast(res->net_type()); + } + + PECallFunction* inner = dynamic_cast( + const_cast(link->peek_chain_prefix())); + if (inner == 0) + return 0; + + const netclass_t* recv_class = resolve_call_chain_prefix_class(des, scope, inner); + if (recv_class == 0) + return 0; + + perm_string mname = peek_tail_name(link->peek_path()); + NetScope* mscope = recv_class->method_from_name(mname); + if (mscope == 0) + return 0; + NetFuncDef* mdef = mscope->func_def(); + if (mdef == 0) + return 0; + NetNet* mres = mscope->find_signal(mscope->basename()); + if (mres == 0) + return 0; + return dynamic_cast(mres->net_type()); +} + static ivl_nature_t find_access_function(const pform_scoped_name_t &path) { if (path.package || path.name.size() != 1) @@ -1752,9 +1799,53 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*, return 0; } +unsigned PECallFunction::test_width_chain_(Design*des, NetScope*scope, + width_mode_t&) +{ + PECallFunction* inner_pf = dynamic_cast(chain_prefix_); + if (inner_pf == 0) { + expr_width_ = 0; + return 0; + } + + const netclass_t* cls = resolve_call_chain_prefix_class(des, scope, inner_pf); + if (cls == 0) { + expr_width_ = 0; + return 0; + } + + perm_string mname = peek_tail_name(path_); + NetScope* mscope = cls->method_from_name(mname); + if (mscope == 0) { + expr_width_ = 0; + return 0; + } + + NetFuncDef* mdef = mscope->func_def(); + if (mdef == 0 || mdef->is_void()) { + expr_width_ = 0; + return 0; + } + + NetNet* mres = mscope->find_signal(mscope->basename()); + if (mres == 0) { + expr_width_ = 0; + return 0; + } + + expr_type_ = mres->data_type(); + expr_width_ = mres->vector_width(); + min_width_ = expr_width_; + signed_flag_ = mres->get_signed(); + return expr_width_; +} + unsigned PECallFunction::test_width(Design*des, NetScope*scope, width_mode_t&mode) { + if (chain_prefix_) + return test_width_chain_(des, scope, mode); + if (debug_elaborate) { cerr << get_fileline() << ": PECallFunction::test_width: " << "path_: " << path_ << endl; @@ -2902,6 +2993,9 @@ NetExpr* PECallFunction::elaborate_expr_(Design*des, NetScope*scope, { flags &= ~SYS_TASK_ARG; // don't propagate the SYS_TASK_ARG flag + if (chain_prefix_) + return elaborate_expr_chain_(des, scope, flags); + // Search for the symbol. This should turn up a scope. symbol_search_results search_results; bool search_flag = symbol_search(this, des, scope, path_, UINT_MAX, &search_results); @@ -3267,6 +3361,18 @@ NetExpr* PECallFunction::elaborate_class_method_net_(Design*des, NetScope*scope, NetNet*net, const netclass_t*class_type, perm_string method_name, const vector*src_parms) const +{ + NetESignal*ethis = new NetESignal(net); + ethis->set_line(*this); + return elaborate_class_method_net_this_(des, scope, ethis, class_type, + method_name, src_parms); +} + +NetExpr* PECallFunction::elaborate_class_method_net_this_(Design*des, NetScope*scope, + NetExpr* this_expr, + const netclass_t*class_type, + perm_string method_name, + const vector*src_parms) const { NetScope*method = class_type->method_from_name(method_name); @@ -3295,9 +3401,7 @@ NetExpr* PECallFunction::elaborate_class_method_net_(Design*des, NetScope*scope, vector parms(def->port_count()); ivl_assert(*this, def->port_count() >= 1); - NetESignal*ethis = new NetESignal(net); - ethis->set_line(*this); - parms[0] = ethis; + parms[0] = this_expr; elaborate_arguments_(des, scope, def, false, parms, 1, src_parms); @@ -3307,6 +3411,29 @@ NetExpr* PECallFunction::elaborate_class_method_net_(Design*des, NetScope*scope, return call; } +NetExpr* PECallFunction::elaborate_expr_chain_(Design*des, NetScope*scope, + unsigned flags) const +{ + ivl_assert(*this, chain_prefix_); + NetExpr* inner = chain_prefix_->elaborate_expr(des, scope, -1, flags); + if (inner == 0) + return 0; + + const netclass_t* cls = dynamic_cast(inner->net_type()); + if (cls == 0) { + cerr << get_fileline() << ": error: " + << "The prefix of a chained call (a().b()) must be an expression " + << "with class type." << endl; + des->errors += 1; + delete inner; + return 0; + } + + perm_string method_name = peek_tail_name(path_); + return elaborate_class_method_net_this_(des, scope, inner, cls, + method_name, &parms_); +} + /* * Handle obj.m1().m2(args): arguments apply only to the last call; intermediate * methods must be class methods returning class handles. diff --git a/ivtest/ivltests/sv_call_chain_method1.v b/ivtest/ivltests/sv_call_chain_method1.v new file mode 100644 index 000000000..d6ba43dee --- /dev/null +++ b/ivtest/ivltests/sv_call_chain_method1.v @@ -0,0 +1,25 @@ +// Chained call: function returns class handle, then method on result (a().b()). + +module test; + + class C; + function int f; + f = 7; + endfunction + endclass + + function C get_c; + get_c = new; + endfunction + + initial begin + int x; + x = get_c().f(); + if (x !== 7) begin + $display("FAILED"); + end else begin + $display("PASSED"); + end + end + +endmodule diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index 3ab22372d..c24235408 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -273,6 +273,7 @@ sv_byte_array_string_fail2 vvp_tests/sv_byte_array_string_fail2.json sv_byte_array_string_fail3 vvp_tests/sv_byte_array_string_fail3.json sv_byte_array_string_fail4 vvp_tests/sv_byte_array_string_fail4.json sv_byte_array_string_fail5 vvp_tests/sv_byte_array_string_fail5.json +sv_call_chain_method1 vvp_tests/sv_call_chain_method1.json sv_chained_constructor1 vvp_tests/sv_chained_constructor1.json sv_chained_constructor2 vvp_tests/sv_chained_constructor2.json sv_chained_constructor3 vvp_tests/sv_chained_constructor3.json diff --git a/ivtest/vvp_tests/sv_call_chain_method1.json b/ivtest/vvp_tests/sv_call_chain_method1.json new file mode 100644 index 000000000..edd7b87fd --- /dev/null +++ b/ivtest/vvp_tests/sv_call_chain_method1.json @@ -0,0 +1,9 @@ +{ + "type" : "normal", + "source" : "sv_call_chain_method1.v", + "iverilog-args" : [ "-g2012" ], + "vlog95" : { + "__comment" : "Classes are not supported", + "type" : "CE" + } +} diff --git a/parse.y b/parse.y index dffeb9104..4a0265532 100644 --- a/parse.y +++ b/parse.y @@ -931,7 +931,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %type timeskew_fullskew_opt_remain_active_flag %type assignment_pattern expression expression_opt expr_mintypmax -%type expr_primary_or_typename expr_primary +%type expr_primary_or_typename expr_primary call_chain_expr %type class_new dynamic_array_new %type net_decl_initializer_opt var_decl_initializer_opt initializer_opt %type inc_or_dec_expression inside_expression lpvalue @@ -4238,6 +4238,26 @@ expr_primary_or_typename ; + /* SystemVerilog: a().b() — call a function, then invoke a method on the + returned class handle. Extends with further ".id(args)" as needed. */ + +call_chain_expr + : hierarchy_identifier attribute_list_opt argument_list_parens + { PECallFunction*tmp = pform_make_call_function(@1, *$1, *$3); + delete $1; + delete $2; + delete $3; + $$ = tmp; + } + | call_chain_expr '.' hierarchy_identifier attribute_list_opt argument_list_parens + { PECallFunction*tmp = pform_make_chained_call_function(@2, $1, *$3, *$5); + delete $3; + delete $4; + delete $5; + $$ = tmp; + } + ; + expr_primary : number { assert($1); @@ -4297,6 +4317,11 @@ expr_primary /* The hierarchy_identifier rule matches simple identifiers as well as indexed arrays and part selects */ + /* SV call chains get_c1().f() — must come before bare hierarchy_identifier + so `id (` is not reduced as PEIdent + error. */ + | call_chain_expr + { $$ = $1; + } | hierarchy_identifier { PEIdent*tmp = pform_new_ident(@1, *$1); FILE_NAME(tmp, @1); @@ -4347,13 +4372,6 @@ expr_primary function call. If a system identifier, then a system function call. It can also be a call to a class method (function). */ - | hierarchy_identifier attribute_list_opt argument_list_parens - { PECallFunction*tmp = pform_make_call_function(@1, *$1, *$3); - delete $1; - delete $2; - delete $3; - $$ = tmp; - } | class_hierarchy_identifier argument_list_parens { PECallFunction*tmp = pform_make_call_function(@1, *$1, *$2); delete $1; diff --git a/pform.cc b/pform.cc index 17b8ebfdf..0f5b3578f 100644 --- a/pform.cc +++ b/pform.cc @@ -975,6 +975,24 @@ PECallFunction* pform_make_call_function(const struct vlltype&loc, return tmp; } +PECallFunction* pform_make_chained_call_function(const struct vlltype&loc, + PExpr*prefix, + const pform_name_t&method, + const list &parms) +{ + if (!gn_system_verilog()) { + pform_requires_sv(loc, "Chained calls like a().b()"); + delete prefix; + return new PECallFunction(method, parms); + } + + check_potential_imports(loc, method.front().name, true); + + PECallFunction*tmp = new PECallFunction(prefix, method, parms); + FILE_NAME(tmp, loc); + return tmp; +} + PCallTask* pform_make_call_task(const struct vlltype&loc, const pform_name_t&name, const list &parms) diff --git a/pform.h b/pform.h index 610dd8390..598eb3988 100644 --- a/pform.h +++ b/pform.h @@ -333,6 +333,10 @@ extern void pform_set_type_referenced(const struct vlltype&loc, const char*name) extern PECallFunction* pform_make_call_function(const struct vlltype&loc, const pform_name_t&name, const std::list &parms); +extern PECallFunction* pform_make_chained_call_function(const struct vlltype&loc, + PExpr*prefix, + const pform_name_t&method, + const std::list &parms); extern PCallTask* pform_make_call_task(const struct vlltype&loc, const pform_name_t&name, const std::list &parms); diff --git a/pform_dump.cc b/pform_dump.cc index 94913eca6..49de3728f 100644 --- a/pform_dump.cc +++ b/pform_dump.cc @@ -421,6 +421,10 @@ void PEConcat::dump(ostream&out) const void PECallFunction::dump(ostream &out) const { + if (peek_chain_prefix()) { + peek_chain_prefix()->dump(out); + out << "."; + } out << path_ << "(" << parms_ << ")"; } From 29a8a4d05223207a10b40d050f4bc814ac001107 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Mon, 13 Jul 2026 22:48:28 -0700 Subject: [PATCH 2/4] Simplify conditional checks in elab_expr.cc --- elab_expr.cc | 65 +++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/elab_expr.cc b/elab_expr.cc index 65d3da65c..693e51abf 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -62,50 +62,44 @@ bool type_is_vectorable(ivl_variable_type_t type) static const netclass_t* resolve_call_chain_prefix_class(Design*des, NetScope*scope, PECallFunction* link) { - if (link == 0) - return 0; + if (link == 0) return 0; if (link->peek_chain_prefix() == 0) { symbol_search_results sr; - if (!symbol_search(link, des, scope, link->peek_path(), UINT_MAX, &sr)) + if (!symbol_search(link, des, scope, link->peek_path(), UINT_MAX, &sr)) { return 0; - if (!sr.is_scope() || sr.scope->type() != NetScope::FUNC) + } + if (!sr.is_scope() || sr.scope->type() != NetScope::FUNC) { return 0; + } NetFuncDef* fundef = sr.scope->func_def(); ivl_assert(*link, fundef); NetScope* dscope = fundef->scope(); NetNet* res = dscope->find_signal(dscope->basename()); - if (res == 0) - return 0; + if (res == 0) return 0; return dynamic_cast(res->net_type()); } PECallFunction* inner = dynamic_cast( const_cast(link->peek_chain_prefix())); - if (inner == 0) - return 0; + if (inner == 0) return 0; const netclass_t* recv_class = resolve_call_chain_prefix_class(des, scope, inner); - if (recv_class == 0) - return 0; + if (recv_class == 0) return 0; perm_string mname = peek_tail_name(link->peek_path()); NetScope* mscope = recv_class->method_from_name(mname); - if (mscope == 0) - return 0; + if (mscope == 0) return 0; NetFuncDef* mdef = mscope->func_def(); - if (mdef == 0) - return 0; + if (mdef == 0) return 0; NetNet* mres = mscope->find_signal(mscope->basename()); - if (mres == 0) - return 0; + if (mres == 0) return 0; return dynamic_cast(mres->net_type()); } static ivl_nature_t find_access_function(const pform_scoped_name_t &path) { - if (path.package || path.name.size() != 1) - return nullptr; + if (path.package || path.name.size() != 1) return nullptr; return access_function_nature[peek_tail_name(path)]; } @@ -1802,36 +1796,22 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*, unsigned PECallFunction::test_width_chain_(Design*des, NetScope*scope, width_mode_t&) { + expr_width_ = 0; PECallFunction* inner_pf = dynamic_cast(chain_prefix_); - if (inner_pf == 0) { - expr_width_ = 0; - return 0; - } + if (inner_pf == 0) return 0; const netclass_t* cls = resolve_call_chain_prefix_class(des, scope, inner_pf); - if (cls == 0) { - expr_width_ = 0; - return 0; - } + if (cls == 0) return 0; perm_string mname = peek_tail_name(path_); NetScope* mscope = cls->method_from_name(mname); - if (mscope == 0) { - expr_width_ = 0; - return 0; - } + if (mscope == 0) return 0; NetFuncDef* mdef = mscope->func_def(); - if (mdef == 0 || mdef->is_void()) { - expr_width_ = 0; - return 0; - } + if (mdef == 0 || mdef->is_void()) return 0; NetNet* mres = mscope->find_signal(mscope->basename()); - if (mres == 0) { - expr_width_ = 0; - return 0; - } + if (mres == 0) return 0; expr_type_ = mres->data_type(); expr_width_ = mres->vector_width(); @@ -1843,8 +1823,7 @@ unsigned PECallFunction::test_width_chain_(Design*des, NetScope*scope, unsigned PECallFunction::test_width(Design*des, NetScope*scope, width_mode_t&mode) { - if (chain_prefix_) - return test_width_chain_(des, scope, mode); + if (chain_prefix_) return test_width_chain_(des, scope, mode); if (debug_elaborate) { cerr << get_fileline() << ": PECallFunction::test_width: " @@ -2993,8 +2972,7 @@ NetExpr* PECallFunction::elaborate_expr_(Design*des, NetScope*scope, { flags &= ~SYS_TASK_ARG; // don't propagate the SYS_TASK_ARG flag - if (chain_prefix_) - return elaborate_expr_chain_(des, scope, flags); + if (chain_prefix_) return elaborate_expr_chain_(des, scope, flags); // Search for the symbol. This should turn up a scope. symbol_search_results search_results; @@ -3416,8 +3394,7 @@ NetExpr* PECallFunction::elaborate_expr_chain_(Design*des, NetScope*scope, { ivl_assert(*this, chain_prefix_); NetExpr* inner = chain_prefix_->elaborate_expr(des, scope, -1, flags); - if (inner == 0) - return 0; + if (inner == 0) return 0; const netclass_t* cls = dynamic_cast(inner->net_type()); if (cls == 0) { From 55250457ed9d292b8e433c3e4242993e3707f896 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Mon, 13 Jul 2026 22:50:55 -0700 Subject: [PATCH 3/4] Fix formatting in parse.y for call_chain_expr --- parse.y | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parse.y b/parse.y index 4a0265532..43dd95a6a 100644 --- a/parse.y +++ b/parse.y @@ -4320,8 +4320,7 @@ expr_primary /* SV call chains get_c1().f() — must come before bare hierarchy_identifier so `id (` is not reduced as PEIdent + error. */ | call_chain_expr - { $$ = $1; - } + { $$ = $1; } | hierarchy_identifier { PEIdent*tmp = pform_new_ident(@1, *$1); FILE_NAME(tmp, @1); From 67d39803e69168bdd5551fa55e88170ed78d96d4 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Mon, 13 Jul 2026 22:54:26 -0700 Subject: [PATCH 4/4] Fix formatting and add braces for clarity --- PExpr.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/PExpr.cc b/PExpr.cc index c9b35aa4d..b2151f970 100644 --- a/PExpr.cc +++ b/PExpr.cc @@ -291,21 +291,25 @@ PECallFunction::~PECallFunction() void PECallFunction::declare_implicit_nets(LexicalScope*scope, NetNet::Type type) { - if (chain_prefix_) + if (chain_prefix_) { chain_prefix_->declare_implicit_nets(scope, type); + } for (const auto &parm : parms_) { - if (parm.parm) + if (parm.parm) { parm.parm->declare_implicit_nets(scope, type); + } } } bool PECallFunction::has_aa_term(Design*des, NetScope*scope) const { - if (chain_prefix_ && chain_prefix_->has_aa_term(des, scope)) + if (chain_prefix_ && chain_prefix_->has_aa_term(des, scope)) { return true; + } for (const auto &parm : parms_) { - if (parm.parm && parm.parm->has_aa_term(des, scope)) + if (parm.parm && parm.parm->has_aa_term(des, scope)) { return true; + } } return false; }