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).
This commit is contained in:
mjoekhan 2026-07-01 12:45:16 +05:00
parent 15689210c1
commit 4a505546a1
10 changed files with 260 additions and 15 deletions

View File

@ -271,12 +271,28 @@ PECallFunction::PECallFunction(perm_string n, const list<named_pexpr_t> &parms)
{ {
} }
PECallFunction::PECallFunction(PExpr* chain_prefix, const pform_name_t &method,
const vector<named_pexpr_t> &parms)
: path_(method), parms_(parms), chain_prefix_(chain_prefix), is_overridden_(false)
{
}
PECallFunction::PECallFunction(PExpr* chain_prefix, const pform_name_t &method,
const list<named_pexpr_t> &parms)
: path_(method), parms_(parms.begin(), parms.end()),
chain_prefix_(chain_prefix), is_overridden_(false)
{
}
PECallFunction::~PECallFunction() PECallFunction::~PECallFunction()
{ {
delete chain_prefix_;
} }
void PECallFunction::declare_implicit_nets(LexicalScope*scope, NetNet::Type type) 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_) { for (const auto &parm : parms_) {
if (parm.parm) if (parm.parm)
parm.parm->declare_implicit_nets(scope, type); 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 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_) { for (const auto &parm : parms_) {
if (parm.parm) if (parm.parm && parm.parm->has_aa_term(des, scope))
flag |= parm.parm->has_aa_term(des, scope); return true;
} }
return flag; return false;
} }
PEConcat::PEConcat(const list<PExpr*>&p, PExpr*r) PEConcat::PEConcat(const list<PExpr*>&p, PExpr*r)

22
PExpr.h
View File

@ -920,8 +920,18 @@ class PECallFunction : public PExpr {
explicit PECallFunction(const pform_name_t &n, const std::list<named_pexpr_t> &parms); explicit PECallFunction(const pform_name_t &n, const std::list<named_pexpr_t> &parms);
explicit PECallFunction(perm_string n, const std::list<named_pexpr_t> &parms); explicit PECallFunction(perm_string n, const std::list<named_pexpr_t> &parms);
// SystemVerilog: prefix().method(args) — prefix elaborates to a class handle.
explicit PECallFunction(PExpr* chain_prefix, const pform_name_t &method,
const std::vector<named_pexpr_t> &parms);
explicit PECallFunction(PExpr* chain_prefix, const pform_name_t &method,
const std::list<named_pexpr_t> &parms);
~PECallFunction() override; ~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 dump(std::ostream &) const override;
virtual void declare_implicit_nets(LexicalScope*scope, NetNet::Type type) override; virtual void declare_implicit_nets(LexicalScope*scope, NetNet::Type type) override;
@ -940,6 +950,8 @@ class PECallFunction : public PExpr {
private: private:
pform_scoped_name_t path_; pform_scoped_name_t path_;
std::vector<named_pexpr_t> parms_; std::vector<named_pexpr_t> parms_;
// If non-null, this call is prefix().tail_name(...) (SV method chain).
PExpr* chain_prefix_ = nullptr;
// For system functions. // For system functions.
bool is_overridden_; bool is_overridden_;
@ -985,8 +997,18 @@ class PECallFunction : public PExpr {
perm_string method_name, perm_string method_name,
const std::vector<named_pexpr_t>*src_parms) const; const std::vector<named_pexpr_t>*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<named_pexpr_t>*src_parms) const;
NetExpr* elaborate_expr_method_chained_(Design*des, NetScope*scope, NetExpr* elaborate_expr_method_chained_(Design*des, NetScope*scope,
symbol_search_results&search_results) const; 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);
}; };
/* /*

View File

@ -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<const netclass_t*>(res->net_type());
}
PECallFunction* inner = dynamic_cast<PECallFunction*>(
const_cast<PExpr*>(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<const netclass_t*>(mres->net_type());
}
static ivl_nature_t find_access_function(const pform_scoped_name_t &path) static ivl_nature_t find_access_function(const pform_scoped_name_t &path)
{ {
if (path.package || path.name.size() != 1) if (path.package || path.name.size() != 1)
@ -1752,9 +1799,53 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*,
return 0; return 0;
} }
unsigned PECallFunction::test_width_chain_(Design*des, NetScope*scope,
width_mode_t&)
{
PECallFunction* inner_pf = dynamic_cast<PECallFunction*>(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, unsigned PECallFunction::test_width(Design*des, NetScope*scope,
width_mode_t&mode) width_mode_t&mode)
{ {
if (chain_prefix_)
return test_width_chain_(des, scope, mode);
if (debug_elaborate) { if (debug_elaborate) {
cerr << get_fileline() << ": PECallFunction::test_width: " cerr << get_fileline() << ": PECallFunction::test_width: "
<< "path_: " << path_ << endl; << "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 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. // Search for the symbol. This should turn up a scope.
symbol_search_results search_results; symbol_search_results search_results;
bool search_flag = symbol_search(this, des, scope, path_, UINT_MAX, &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, NetNet*net, const netclass_t*class_type,
perm_string method_name, perm_string method_name,
const vector<named_pexpr_t>*src_parms) const const vector<named_pexpr_t>*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<named_pexpr_t>*src_parms) const
{ {
NetScope*method = class_type->method_from_name(method_name); NetScope*method = class_type->method_from_name(method_name);
@ -3295,9 +3401,7 @@ NetExpr* PECallFunction::elaborate_class_method_net_(Design*des, NetScope*scope,
vector<NetExpr*> parms(def->port_count()); vector<NetExpr*> parms(def->port_count());
ivl_assert(*this, def->port_count() >= 1); ivl_assert(*this, def->port_count() >= 1);
NetESignal*ethis = new NetESignal(net); parms[0] = this_expr;
ethis->set_line(*this);
parms[0] = ethis;
elaborate_arguments_(des, scope, def, false, parms, 1, src_parms); 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; 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<const netclass_t*>(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 * Handle obj.m1().m2(args): arguments apply only to the last call; intermediate
* methods must be class methods returning class handles. * methods must be class methods returning class handles.

View File

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

View File

@ -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_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_fail4 vvp_tests/sv_byte_array_string_fail4.json
sv_byte_array_string_fail5 vvp_tests/sv_byte_array_string_fail5.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_constructor1 vvp_tests/sv_chained_constructor1.json
sv_chained_constructor2 vvp_tests/sv_chained_constructor2.json sv_chained_constructor2 vvp_tests/sv_chained_constructor2.json
sv_chained_constructor3 vvp_tests/sv_chained_constructor3.json sv_chained_constructor3 vvp_tests/sv_chained_constructor3.json

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_call_chain_method1.v",
"iverilog-args" : [ "-g2012" ],
"vlog95" : {
"__comment" : "Classes are not supported",
"type" : "CE"
}
}

34
parse.y
View File

@ -931,7 +931,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <spec_optional_args> timeskew_fullskew_opt_remain_active_flag %type <spec_optional_args> timeskew_fullskew_opt_remain_active_flag
%type <expr> assignment_pattern expression expression_opt expr_mintypmax %type <expr> assignment_pattern expression expression_opt expr_mintypmax
%type <expr> expr_primary_or_typename expr_primary %type <expr> expr_primary_or_typename expr_primary call_chain_expr
%type <expr> class_new dynamic_array_new %type <expr> class_new dynamic_array_new
%type <expr> net_decl_initializer_opt var_decl_initializer_opt initializer_opt %type <expr> net_decl_initializer_opt var_decl_initializer_opt initializer_opt
%type <expr> inc_or_dec_expression inside_expression lpvalue %type <expr> 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 expr_primary
: number : number
{ assert($1); { assert($1);
@ -4297,6 +4317,11 @@ expr_primary
/* The hierarchy_identifier rule matches simple identifiers as well as /* The hierarchy_identifier rule matches simple identifiers as well as
indexed arrays and part selects */ 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 | hierarchy_identifier
{ PEIdent*tmp = pform_new_ident(@1, *$1); { PEIdent*tmp = pform_new_ident(@1, *$1);
FILE_NAME(tmp, @1); FILE_NAME(tmp, @1);
@ -4347,13 +4372,6 @@ expr_primary
function call. If a system identifier, then a system function function call. If a system identifier, then a system function
call. It can also be a call to a class method (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 | class_hierarchy_identifier argument_list_parens
{ PECallFunction*tmp = pform_make_call_function(@1, *$1, *$2); { PECallFunction*tmp = pform_make_call_function(@1, *$1, *$2);
delete $1; delete $1;

View File

@ -975,6 +975,24 @@ PECallFunction* pform_make_call_function(const struct vlltype&loc,
return tmp; return tmp;
} }
PECallFunction* pform_make_chained_call_function(const struct vlltype&loc,
PExpr*prefix,
const pform_name_t&method,
const list<named_pexpr_t> &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, PCallTask* pform_make_call_task(const struct vlltype&loc,
const pform_name_t&name, const pform_name_t&name,
const list<named_pexpr_t> &parms) const list<named_pexpr_t> &parms)

View File

@ -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, extern PECallFunction* pform_make_call_function(const struct vlltype&loc,
const pform_name_t&name, const pform_name_t&name,
const std::list<named_pexpr_t> &parms); const std::list<named_pexpr_t> &parms);
extern PECallFunction* pform_make_chained_call_function(const struct vlltype&loc,
PExpr*prefix,
const pform_name_t&method,
const std::list<named_pexpr_t> &parms);
extern PCallTask* pform_make_call_task(const struct vlltype&loc, extern PCallTask* pform_make_call_task(const struct vlltype&loc,
const pform_name_t&name, const pform_name_t&name,
const std::list<named_pexpr_t> &parms); const std::list<named_pexpr_t> &parms);

View File

@ -421,6 +421,10 @@ void PEConcat::dump(ostream&out) const
void PECallFunction::dump(ostream &out) const void PECallFunction::dump(ostream &out) const
{ {
if (peek_chain_prefix()) {
peek_chain_prefix()->dump(out);
out << ".";
}
out << path_ << "(" << parms_ << ")"; out << path_ << "(" << parms_ << ")";
} }