From 24c129380110be5f95948dc1bcfcf9b2b610f595 Mon Sep 17 00:00:00 2001 From: mjoekhan Date: Tue, 14 Apr 2026 00:07:21 +0500 Subject: [PATCH] Add SystemVerilog chained calls a().b() and regression Implement call_chain_expr in the parser, PECallFunction chain prefixes, elaboration for class methods on returned handles, and prefix class resolution for width checks on multi-hop chains. Place call_chain_expr before bare hierarchy_identifier in expr_primary so id ( is not reduced as a lone ident. Document behavior and developer notes in devel/sv_call_chain.md (including that iverilog uses the installed lib/ivl/ivl binary). Add ivtest sv_call_chain_method1 normal test with -g2012. Made-with: Cursor --- PExpr.cc | 25 +++- PExpr.h | 22 ++++ devel/sv_call_chain.md | 72 +++++++++++ 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 + 11 files changed, 332 insertions(+), 15 deletions(-) create mode 100644 devel/sv_call_chain.md 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 8a32b0506..6228a2eea 100644 --- a/PExpr.cc +++ b/PExpr.cc @@ -261,12 +261,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); @@ -275,12 +291,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 f7d2e10d5..36e8884b8 100644 --- a/PExpr.h +++ b/PExpr.h @@ -925,8 +925,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; @@ -945,6 +955,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_; @@ -990,8 +1002,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/devel/sv_call_chain.md b/devel/sv_call_chain.md new file mode 100644 index 000000000..fa0f73e0d --- /dev/null +++ b/devel/sv_call_chain.md @@ -0,0 +1,72 @@ +# SystemVerilog chained calls: `a().b()` + +This note describes the parser and elaboration support for **chained calls**: a +function call whose value is a class handle, followed by one or more +`.method(args)` segments (e.g. `get_c().f()`, `a().b().c()`). + +## Language shape + +- **Parse:** A dedicated nonterminal `call_chain_expr` in `parse.y` builds a + left-associated chain: + - `hierarchy_identifier attribute_list_opt argument_list_parens` — first call; + - `call_chain_expr '.' hierarchy_identifier attribute_list_opt argument_list_parens` — each further segment. +- **`expr_primary`** includes `call_chain_expr` **before** the bare + `hierarchy_identifier` alternative so `id (` is parsed as a call, not as an + identifier plus a stray `(`. + +## Parse tree (`PExpr`) + +- **`PECallFunction`** (`PExpr.h` / `PExpr.cc`): + - Optional **`chain_prefix_`**: inner `PExpr` for the prefix (another + `PECallFunction` for longer chains). + - Constructors and accessors: `peek_path()`, `peek_chain_prefix()`. +- **`pform_make_chained_call_function`** (`pform.cc`) — requires SystemVerilog; + builds `PECallFunction(prefix, method_name, args)`. + +## Elaboration (`elab_expr.cc`) + +- **`PECallFunction::elaborate_expr_`** delegates to **`elaborate_expr_chain_`** + when `chain_prefix_` is set. +- **`elaborate_class_method_net_this_`** passes the elaborated prefix as the + implicit `this` argument (first parameter slot), including nested `NetEUFunc` + for inner calls — not only `NetESignal(net)`. +- **`resolve_call_chain_prefix_class`** (static helper) resolves the **class + type** of the prefix for multi-hop chains (e.g. width checks), walking the + chain prefix recursively instead of searching only the tail name. + +## Dump / debug + +- **`pform_dump.cc`** prints chained calls with a `prefix.` prefix before the + method path. + +## Regression + +- **`ivtest/ivltests/sv_call_chain_method1.v`** +- **`ivtest/vvp_tests/sv_call_chain_method1.json`** (`-g2012`) +- Listed in **`ivtest/regress-vvp.list`** as `sv_call_chain_method1`. + +## Using a locally built `iverilog` + +`iverilog` invokes the installed compiler under your prefix, typically +`$PREFIX/lib/ivl/ivl`, not the `ivl` binary in the build tree. After changing +the parser, **reinstall** or copy the new `ivl` into that lib directory so +`iverilog` picks up the change; otherwise chained-call syntax may still fail +with a **syntax error** while a direct `./ivl -C...` test from the build tree +succeeds. + +```bash +# Example after building in-tree +make install +# or copy only the compiler binary to your existing install +cp ivl "$PREFIX/lib/ivl/ivl" +``` + +## Related files (non-exhaustive) + +| Area | Files | +|------------|--------| +| Grammar | `parse.y` (`call_chain_expr`, `expr_primary`) | +| Parse form | `pform.cc`, `pform.h` | +| AST | `PExpr.h`, `PExpr.cc` | +| Elab | `elab_expr.cc` | +| Dump | `pform_dump.cc` | diff --git a/elab_expr.cc b/elab_expr.cc index 88dfcfc94..35b1d18b7 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) @@ -1742,9 +1789,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; @@ -2891,6 +2982,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); @@ -3256,6 +3350,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); @@ -3276,9 +3382,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); @@ -3288,6 +3392,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 a85141024..df3fd4190 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -222,6 +222,7 @@ sv_array_cassign6 vvp_tests/sv_array_cassign6.json sv_array_cassign7 vvp_tests/sv_array_cassign7.json sv_array_cassign8 vvp_tests/sv_array_cassign8.json sv_automatic_2state vvp_tests/sv_automatic_2state.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 d1767a86b..12e7e43b1 100644 --- a/parse.y +++ b/parse.y @@ -756,7 +756,7 @@ Module::port_t *module_declare_port(const YYLTYPE&loc, char *id, %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 var_decl_initializer_opt initializer_opt %type inc_or_dec_expression inside_expression lpvalue @@ -3869,6 +3869,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); @@ -3928,6 +3948,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); @@ -3978,13 +4003,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 c7e337b63..277312b05 100644 --- a/pform.cc +++ b/pform.cc @@ -938,6 +938,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 f7d5b491b..12d0e5704 100644 --- a/pform.h +++ b/pform.h @@ -324,6 +324,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 6dc130687..688ecf54c 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_ << ")"; }