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
This commit is contained in:
parent
1a10f9d57b
commit
24c1293801
25
PExpr.cc
25
PExpr.cc
|
|
@ -261,12 +261,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()
|
||||
{
|
||||
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<PExpr*>&p, PExpr*r)
|
||||
|
|
|
|||
22
PExpr.h
22
PExpr.h
|
|
@ -925,8 +925,18 @@ class PECallFunction : public PExpr {
|
|||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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<named_pexpr_t> 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<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,
|
||||
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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
133
elab_expr.cc
133
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<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)
|
||||
{
|
||||
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<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,
|
||||
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<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);
|
||||
|
||||
|
|
@ -3276,9 +3382,7 @@ NetExpr* PECallFunction::elaborate_class_method_net_(Design*des, NetScope*scope,
|
|||
vector<NetExpr*> 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<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
|
||||
* methods must be class methods returning class handles.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
34
parse.y
|
|
@ -756,7 +756,7 @@ Module::port_t *module_declare_port(const YYLTYPE&loc, char *id,
|
|||
%type <spec_optional_args> timeskew_fullskew_opt_remain_active_flag
|
||||
|
||||
%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> var_decl_initializer_opt initializer_opt
|
||||
%type <expr> 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;
|
||||
|
|
|
|||
18
pform.cc
18
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<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,
|
||||
const pform_name_t&name,
|
||||
const list<named_pexpr_t> &parms)
|
||||
|
|
|
|||
4
pform.h
4
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<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,
|
||||
const pform_name_t&name,
|
||||
const std::list<named_pexpr_t> &parms);
|
||||
|
|
|
|||
|
|
@ -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_ << ")";
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue