Merge pull request #859 from larsclausen/func-empty-arg

Improvements for calling functions with empty arguments
This commit is contained in:
Stephen Williams 2023-01-16 19:26:23 -08:00 committed by GitHub
commit e740e4b3f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 214 additions and 76 deletions

View File

@ -286,7 +286,8 @@ PECallFunction::~PECallFunction()
void PECallFunction::declare_implicit_nets(LexicalScope*scope, NetNet::Type type)
{
for (unsigned idx = 0 ; idx < parms_.size() ; idx += 1) {
parms_[idx]->declare_implicit_nets(scope, type);
if (parms_[idx])
parms_[idx]->declare_implicit_nets(scope, type);
}
}
@ -294,7 +295,8 @@ bool PECallFunction::has_aa_term(Design*des, NetScope*scope) const
{
bool flag = false;
for (unsigned idx = 0 ; idx < parms_.size() ; idx += 1) {
flag = parms_[idx]->has_aa_term(des, scope) || flag;
if (parms_[idx])
flag |= parms_[idx]->has_aa_term(des, scope);
}
return flag;
}

View File

@ -1816,17 +1816,7 @@ NetExpr* PECallFunction::elaborate_sfunc_(Design*des, NetScope*scope,
return cast_to_width_(sub, expr_wid);
}
/* How many parameters are there? The Verilog language allows
empty parameters in certain contexts, so the parser will
allow things like func(1,,3). It will also cause func() to
be interpreted as a single empty parameter.
Functions cannot really take empty parameters, but the
case ``func()'' is the same as no parameters at all. So
catch that special case here. */
unsigned nparms = parms_.size();
if ((nparms == 1) && (parms_[0] == 0))
nparms = 0;
NetESFunc*fun = new NetESFunc(name, expr_type_, expr_width_, nparms, is_overridden_);
fun->set_line(*this);
@ -2937,11 +2927,7 @@ unsigned PECallFunction::elaborate_arguments_(Design*des, NetScope*scope,
const unsigned parm_count = parms.size() - parm_off;
const unsigned actual_count = parms_.size();
/* The parser can't distinguish between a function call with
no arguments and a function call with one empty argument,
and always supplies one empty argument. Handle the no
argument case here. */
if ((parm_count == 0) && (actual_count == 1) && (parms_[0] == 0))
if (parm_count == 0 && actual_count == 0)
return 0;
if (actual_count > parm_count) {
@ -2984,7 +2970,7 @@ unsigned PECallFunction::elaborate_arguments_(Design*des, NetScope*scope,
<< "requires SystemVerilog." << endl;
des->errors += 1;
}
parms[pidx] = def->port_defe(pidx);
parms[pidx] = def->port_defe(pidx)->dup_expr();
} else {
missing_parms += 1;
@ -6494,7 +6480,7 @@ NetExpr* PENewClass::elaborate_expr_constructor_(Design*des, NetScope*scope,
// Ran out of explicit arguments. Is there a default
// argument we can use?
if (NetExpr*tmp = def->port_defe(idx)) {
parms[idx] = tmp;
parms[idx] = tmp->dup_expr();
continue;
}

View File

@ -3344,7 +3344,7 @@ NetProc* PChainConstructor::elaborate(Design*des, NetScope*scope) const
}
if (NetExpr*tmp = def->port_defe(idx)) {
parms[idx] = tmp;
parms[idx] = tmp->dup_expr();
continue;
}
@ -3508,13 +3508,6 @@ NetProc* PCallTask::elaborate_sys(Design*des, NetScope*scope) const
}
unsigned parm_count = parms_.size();
/* Catch the special case that the system task has no
parameters. The "()" string will be parsed as a single
empty parameter, when we really mean no parameters at all. */
if ((parm_count== 1) && (parms_[0] == 0))
parm_count = 0;
vector<NetExpr*>eparms (parm_count);
perm_string name = peek_tail_name(path_);
@ -3660,10 +3653,7 @@ NetProc* PCallTask::elaborate_sys_task_method_(Design*des, NetScope*scope,
NetESignal*sig = new NetESignal(net);
sig->set_line(*this);
/* If there is a single NULL argument then ignore it since it is
* left over from the parser and is not needed by the method. */
unsigned nparms = parms_.size();
if ((nparms == 1) && (parms_[0] == 0)) nparms = 0;
vector<NetExpr*>argv (1 + nparms);
argv[0] = sig;
@ -4172,9 +4162,9 @@ NetProc* PCallTask::elaborate_build_call_(Design*des, NetScope*scope,
"requires SystemVerilog." << endl;
des->errors += 1;
}
rv = def->port_defe(idx);
rv = def->port_defe(idx)->dup_expr();
if (lv_type==IVL_VT_BOOL||lv_type==IVL_VT_LOGIC)
rv = pad_to_width(rv->dup_expr(), wid, *this);
rv = pad_to_width(rv, wid, *this);
} else {
cerr << get_fileline() << ": error: "
@ -4355,12 +4345,6 @@ bool PCallTask::elaborate_elab(Design*des, NetScope*scope) const
unsigned parm_count = parms_.size();
/* Catch the special case that the elaboration task has no
parameters. The "()" string will be parsed as a single
empty parameter, when we really mean no parameters at all. */
if ((parm_count== 1) && (parms_[0] == 0))
parm_count = 0;
perm_string name = peek_tail_name(path_);
if (!gn_system_verilog()) {

View File

@ -0,0 +1,33 @@
// Check that it is possible to call functins with empty arguments if they have
// default values.
module test;
bit failed = 1'b0;
`define check(expr, val) \
if (expr !== val) begin \
$display("FAILED. %s, expected %d, got %d", `"expr`", val, expr); \
failed = 1'b1; \
end
function integer f(integer a = 1, integer b = 2, integer c = 3);
return a * 100 + b * 10 + c;
endfunction
initial begin
`check(f(4, 5, 6), 456);
`check(f(4, 5, ), 453);
`check(f(4, , 6), 426);
`check(f( , 5, 6), 156);
`check(f(4, , ), 423);
`check(f( , 5, ), 153);
`check(f( , , 6), 126);
`check(f( , , ), 123);
if (!failed) begin
$display("PASSED");
end
end
endmodule

View File

@ -0,0 +1,29 @@
// Check that it is possible to call functins with empty arguments if they have
// default values. Check that this works if the function call is in a module
// port binding expression.
module M (input [31:0] x, input [31:0] y);
initial begin
#1
if (x === 623 && y == 624) begin
$display("PASSED");
end else begin
$display("FAILED");
end
end
endmodule
module test;
function [31:0] f(reg [31:0] a, reg [31:0] b = 2, reg [31:0] c = 3);
return a * 100 + b * 10 + c;
endfunction
M i_m (
.x(f(6, )),
.y(f(6, , 4))
);
endmodule

View File

@ -0,0 +1,41 @@
// Check that it is possible to call functins with empty arguments if they have
// default values. Check that this works if the function call is part of a force
// statement in an automatic context.
module test;
bit failed = 1'b0;
`define check(expr, val) \
if (expr !== val) begin \
$display("FAILED. %s, expected %d, got %d", `"expr`", val, expr); \
failed = 1'b1; \
end
integer x, y, z, w;
function automatic integer f(integer a = 1, integer b = 2, integer c = 3);
return a * 100 + b * 10 + c;
endfunction
task automatic t;
force x = f(4, , 6);
force y = f(4, 5, );
force z = f(4, , );
force w = f( , , 6);
endtask
initial begin
t;
`check(x, 426);
`check(y, 453);
`check(z, 423);
`check(w, 126);
if (!failed) begin
$display("PASSED");
end
end
endmodule

View File

@ -0,0 +1,16 @@
// Check that passing too many empty arguments to a function results in an error.
module test;
function f(integer a = 1, integer b = 2, integer c = 3);
return a + 10 * b + 100 * c;
endfunction
initial begin
integer x;
x = f( , , ,); // This should fail. 4 empty args, even though the function
// only takes 3 args
$display("FAILED");
end
endmodule

View File

@ -0,0 +1,16 @@
// Check that passing additional empty arguments to a function results in an error.
module test;
function f(integer a = 1, integer b = 2, integer c = 3);
return a + 10 * b + 100 * c;
endfunction
initial begin
integer x;
x = f(4, 5, 6, ); // This should fail. 4 empty args, even though the function
// only takes 3 args
$display("FAILED");
end
endmodule

View File

@ -0,0 +1,16 @@
// Check that passing empty arguments to a function without any ports is an
// error.
module test;
function f();
return 42;
endfunction
initial begin
integer x;
x = f( , ); // This should fail. The function takes no arguments.
$display("FAILED");
end
endmodule

View File

@ -0,0 +1,16 @@
// Check that an error is reported when passing an empty function argument for a
// port without a default value.
module test;
function f(integer a, integer b = 2);
return a + 10 * b + 100 * c;
endfunction
initial begin
integer x;
x = f( , 3); // This should fail. No default value specified.
$display("FAILED");
end
endmodule

View File

@ -313,6 +313,13 @@ fork_join_any normal,-g2009 ivltests
fork_join_dis normal,-g2009 ivltests
fork_join_none normal,-g2009 ivltests
fr49 normal,-g2009 ivltests
func_empty_arg1 normal,-g2005-sv ivltests
func_empty_arg2 normal,-g2005-sv ivltests
func_empty_arg3 normal,-g2005-sv ivltests
func_empty_arg_fail1 CE,-g2005-sv ivltests
func_empty_arg_fail2 CE,-g2005-sv ivltests
func_empty_arg_fail3 CE,-g2005-sv ivltests
func_empty_arg_fail4 CE,-g2005-sv ivltests
func_init_var1 normal,-g2009 ivltests
func_init_var2 normal,-g2009 ivltests
func_init_var3 normal,-g2009 ivltests

View File

@ -82,6 +82,7 @@ automatic_task3 CE ivltests
br942 CE ivltests
br_gh531 CE ivltests
def_nettype CE ivltests
func_empty_arg3 CE,-g2005-sv ivltests
func_init_var1 CE,-pallowsigned=1 ivltests
func_init_var2 CE,-pallowsigned=1 ivltests
func_init_var3 CE,-pallowsigned=1 ivltests

View File

@ -82,15 +82,6 @@ bool PECallFunction::check_call_matches_definition_(Design*des, NetScope*dscope)
{
assert(dscope);
/* How many parameters have I got? Normally the size of the
list is correct, but there is the special case of a list of
1 nil pointer. This is how the parser tells me of no
parameter. In other words, ``func()'' is 1 nil parameter. */
unsigned parms_count = parms_.size();
if ((parms_count == 1) && (parms_[0] == 0))
parms_count = 0;
if (dscope->type() != NetScope::FUNC) {
cerr << get_fileline() << ": error: Attempt to call scope "
<< scope_path(dscope) << " as a function." << endl;

58
parse.y
View File

@ -174,15 +174,13 @@ template <class T> void append(vector<T>&out, const std::vector<T>&in)
}
/*
* Look at the list and pull null pointers off the end.
* The parser parses an empty argument list as an argument list with an single
* empty argument. Fix this up here and replace it with an empty list.
*/
static void strip_tail_items(list<PExpr*>*lst)
static void argument_list_fixup(list<PExpr*>*lst)
{
while (! lst->empty()) {
if (lst->back() != 0)
return;
lst->pop_back();
}
if (lst->size() == 1 && !lst->front())
lst->clear();
}
/*
@ -641,7 +639,7 @@ static void current_function_set_statement(const YYLTYPE&loc, std::vector<Statem
%type <expr> delay_value delay_value_simple
%type <exprs> delay1 delay3 delay3_opt delay_value_list
%type <exprs> expression_list_with_nuls expression_list_proper
%type <exprs> argument_list_parens_opt
%type <exprs> argument_list_parens argument_list_parens_opt
%type <exprs> cont_assign cont_assign_list
%type <decl_assignment> variable_decl_assignment
@ -984,9 +982,7 @@ class_scope
class_new /* IEEE1800-2005 A.2.4 */
: K_new argument_list_parens_opt
{ std::list<PExpr*>*expr_list = $2;
strip_tail_items(expr_list);
PENewClass*tmp = new PENewClass(*expr_list);
{ PENewClass*tmp = new PENewClass(*$2);
FILE_NAME(tmp, @1);
delete $2;
$$ = tmp;
@ -994,9 +990,7 @@ class_new /* IEEE1800-2005 A.2.4 */
// This can't be a class_scope_opt because it will lead to shift/reduce
// conflicts with array_new
| class_scope K_new argument_list_parens_opt
{ std::list<PExpr*>*expr_list = $3;
strip_tail_items(expr_list);
PENewClass *new_expr = new PENewClass(*expr_list, $1);
{ PENewClass *new_expr = new PENewClass(*$3, $1);
FILE_NAME(new_expr, @2);
delete $3;
$$ = new_expr;
@ -3620,12 +3614,22 @@ expression_list_with_nuls
}
;
/* An argument list enclosed in parenthesis. The parser will parse '()' as a
* argument list with an single empty item. We fix this up once the list
* parsing is done by replacing it with the empty list.
*/
argument_list_parens
: '(' expression_list_with_nuls ')'
{ argument_list_fixup($2);
$$ = $2; }
;
/* A task or function can be invoked with the task/function name followed by
* an argument list in parenthesis or with just the task/function name by
* itself. When an argument list is used it might be empty. */
argument_list_parens_opt
: '(' expression_list_with_nuls ')'
{ $$ = $2; }
: argument_list_parens
{ $$ = $1; }
|
{ $$ = new std::list<PExpr*>; }
@ -3757,21 +3761,17 @@ 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 '(' expression_list_with_nuls ')'
{ std::list<PExpr*>*expr_list = $4;
strip_tail_items(expr_list);
PECallFunction*tmp = pform_make_call_function(@1, *$1, *expr_list);
| hierarchy_identifier attribute_list_opt argument_list_parens
{ PECallFunction*tmp = pform_make_call_function(@1, *$1, *$3);
delete $1;
delete $2;
delete expr_list;
delete $3;
$$ = tmp;
}
| class_hierarchy_identifier '(' expression_list_with_nuls ')'
{ list<PExpr*>*expr_list = $3;
strip_tail_items(expr_list);
PECallFunction*tmp = pform_make_call_function(@1, *$1, *expr_list);
| class_hierarchy_identifier argument_list_parens
{ PECallFunction*tmp = pform_make_call_function(@1, *$1, *$2);
delete $1;
delete expr_list;
delete $2;
$$ = tmp;
}
| SYSTEM_IDENTIFIER '(' expression_list_proper ')'
@ -3782,11 +3782,11 @@ expr_primary
delete $3;
$$ = tmp;
}
| package_scope hierarchy_identifier { lex_in_package_scope(0); } '(' expression_list_with_nuls ')'
{ PECallFunction*tmp = new PECallFunction($1, *$2, *$5);
| package_scope hierarchy_identifier { lex_in_package_scope(0); } argument_list_parens
{ PECallFunction*tmp = new PECallFunction($1, *$2, *$4);
FILE_NAME(tmp, @2);
delete $2;
delete $5;
delete $4;
$$ = tmp;
}
| SYSTEM_IDENTIFIER '(' ')'