Add ability to read back return value / Add vec4 stacked user functions.

Also:
- handle functions as arguments to system tasks.
- Cleanup detect of signal as return value.
This commit is contained in:
Stephen Williams 2016-01-24 18:36:26 -08:00
parent 0c91a6b041
commit e435a879fc
15 changed files with 268 additions and 30 deletions

View File

@ -191,9 +191,6 @@ void draw_ufunc_vec4(ivl_expr_t expr)
/* Take in arguments to function and call function code. */
draw_ufunc_preamble(expr);
assert(ivl_signal_dimensions(retval) == 0);
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", retval);
draw_ufunc_epilogue(expr);
}

View File

@ -99,6 +99,12 @@ static int get_vpi_taskfunc_signal_arg(struct args_info *result,
/* This should never happen since we have IVL_EX_SELECT. */
return 0;
} else if (signal_is_return_value(ivl_expr_signal(expr))) {
/* If the signal is the return value of a function,
then this can't be handled as a true signal, so
fall back on general expression processing. */
return 0;
} else if (ivl_expr_signed(expr) !=
ivl_signal_signed(ivl_expr_signal(expr))) {
return 0;

View File

@ -1005,6 +1005,15 @@ static void draw_signal_vec4(ivl_expr_t expr)
{
ivl_signal_t sig = ivl_expr_signal(expr);
/* Special Case: If the signal is the return value of the function,
then use a different opcode to get the value. */
if (signal_is_return_value(sig)) {
assert(ivl_signal_dimensions(sig) == 0);
fprintf(vvp_out, " %%retload/vec4 0; Load %s (draw_signal_vec4)\n",
ivl_signal_basename(sig));
return;
}
/* Handle the simple case, a signal expression that is a
simple vector, no array dimensions. */
if (ivl_signal_dimensions(sig) == 0) {

View File

@ -209,6 +209,17 @@ static void put_vec_to_lval_slice(ivl_lval_t lval, struct vec_slice_info*slice,
ivl_signal_t sig = ivl_lval_sig(lval);
int part_off_idx;
/* Special Case: If the l-value signal is named after its scope,
and the scope is a function, then this is an assign to a return
value and should be handled differently. */
if (signal_is_return_value(sig)) {
assert(ivl_signal_dimensions(sig) == 0);
fprintf(vvp_out, " %%ret/vec4 0; Assign to %s\n",
ivl_signal_basename(sig));
return;
}
/* If the slice of the l-value is a BOOL variable, then cast
the data to a BOOL vector so that the stores can be valid. */
if (ivl_signal_data_type(sig) == IVL_VT_BOOL) {
@ -345,6 +356,16 @@ static void store_vec4_to_lval(ivl_statement_t net)
if (lidx+1 < ivl_stmt_lvals(net))
fprintf(vvp_out, " %%split/vec4 %u;\n", lwid);
/* Special Case: If the l-value signal is named after its scope,
and the scope is a function, then this is an assign to a return
value and should be handled differently. */
if (signal_is_return_value(lsig)) {
assert(ivl_signal_dimensions(lsig) == 0);
fprintf(vvp_out, " %%ret/vec4 0; Assign to %s (store_vec4_to_lval)\n",
ivl_signal_basename(lsig));
continue;
}
if (word_ex) {
/* Handle index into an array */
int word_index = allocate_word();

View File

@ -208,6 +208,10 @@ extern void draw_eval_expr_into_integer(ivl_expr_t expr, unsigned ix);
*/
extern int draw_eval_condition(ivl_expr_t expr);
/*
* Return true if the signal is the return value of a function.
*/
extern int signal_is_return_value(ivl_signal_t sig);
extern int number_is_unknown(ivl_expr_t ex);
extern int number_is_immediate(ivl_expr_t ex, unsigned lim_wid, int negative_is_ok);

View File

@ -290,6 +290,16 @@ static unsigned is_netlist_signal(ivl_net_logic_t net, ivl_nexus_t nex)
return rtn;
}
int signal_is_return_value(ivl_signal_t sig)
{
ivl_scope_t sig_scope = ivl_signal_scope(sig);
if (ivl_scope_type(sig_scope) != IVL_SCT_FUNCTION)
return 0;
if (strcmp(ivl_signal_basename(sig), ivl_scope_basename(sig_scope)) == 0)
return 1;
return 0;
}
/*
* This tests a bufz device against an output receiver, and determines
* if the device can be skipped. If this function returns false, then a
@ -458,6 +468,13 @@ static void draw_reg_in_scope(ivl_signal_t sig)
ivl_signal_basename(sig), ivl_signal_scope(sig));
return;
}
if ((ivl_signal_data_type(sig)==IVL_VT_LOGIC)
&& (ivl_scope_type(ivl_signal_scope(sig))==IVL_SCT_FUNCTION)
&& (strcmp(ivl_signal_basename(sig),ivl_scope_basename(ivl_signal_scope(sig))) == 0)) {
fprintf(vvp_out, "; Variable %s is vec4 return value of scope S_%p\n",
ivl_signal_basename(sig), ivl_signal_scope(sig));
return;
}
const char *datatype_flag = ivl_signal_integer(sig) ? "/i" :
ivl_signal_signed(sig)? "/s" : "";

View File

@ -702,11 +702,14 @@ STRUCTURAL FUNCTION CALLS:
The .ufunc statements define a call to a user defined function.
<label> .ufunc <flabel>, <wid>,
<isymbols> ( <psymbols> ) <rsymbol> <ssymbol>;
<label> .ufunc/real <flabel>, <wid>,
<isymbols> ( <psymbols> ) <ssymbol>;
<label> .ufunc/vec4 <flabel>, <wid>,
<isymbols> ( <psymbols> ) <ssymbol>;
<label> .ufunc/e <flabel>, <wid>, <trigger>,
<isymbols> ( <psymbols> ) <rsymbol> <ssymbol>;
<isymbols> ( <psymbols> ) <ssymbol>;
The first variant is used for functions that only need to be called
when one of their inputs changes value. The second variant is used
@ -729,10 +732,6 @@ list. The <psymbols> are variables that represent the input ports for
the function. The ufunc performs an assignment to these variables
before calling the function.
The <rsymbol> is the variable within the function where the result
will be found when the function code ends. This value is picked up
and propagated to the output of the functor.
The <ssymbol> is the function scope name.
THREAD STATEMENTS:

View File

@ -199,6 +199,9 @@ extern bool of_RELEASE_REG(vthread_t thr, vvp_code_t code);
extern bool of_RELEASE_WR(vthread_t thr, vvp_code_t code);
extern bool of_REPLICATE(vthread_t thr, vvp_code_t code);
extern bool of_RET_REAL(vthread_t thr, vvp_code_t code);
extern bool of_RET_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_RETLOAD_REAL(vthread_t thr, vvp_code_t code);
extern bool of_RETLOAD_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_SCOPY(vthread_t thr, vvp_code_t code);
extern bool of_SET_DAR_OBJ_REAL(vthread_t thr, vvp_code_t code);
extern bool of_SET_DAR_OBJ_STR(vthread_t thr, vvp_code_t code);

View File

@ -250,6 +250,9 @@ static const struct opcode_table_s opcode_table[] = {
{ "%release/wr", of_RELEASE_WR, 2,{OA_FUNC_PTR,OA_BIT1,OA_NONE} },
{ "%replicate", of_REPLICATE, 1,{OA_NUMBER, OA_NONE,OA_NONE} },
{ "%ret/real", of_RET_REAL, 1,{OA_NUMBER, OA_NONE,OA_NONE} },
{ "%ret/vec4", of_RET_VEC4, 1,{OA_NUMBER, OA_NONE,OA_NONE} },
{ "%retload/real",of_RETLOAD_REAL,1,{OA_NUMBER, OA_NONE,OA_NONE} },
{ "%retload/vec4",of_RETLOAD_VEC4,1,{OA_NUMBER, OA_NONE,OA_NONE} },
{ "%scopy", of_SCOPY, 0, {OA_NONE, OA_NONE, OA_NONE} },
{ "%set/dar/obj/real",of_SET_DAR_OBJ_REAL,1,{OA_NUMBER,OA_NONE,OA_NONE} },
{ "%set/dar/obj/str", of_SET_DAR_OBJ_STR, 1,{OA_NUMBER,OA_NONE,OA_NONE} },

View File

@ -209,6 +209,7 @@ static char* strdupnew(char const *str)
".tranif1" { return K_TRANIF1; }
".tranvp" { return K_TRANVP; }
".ufunc/real" { return K_UFUNC_REAL; }
".ufunc/vec4" { return K_UFUNC_VEC4; }
".ufunc/e" { return K_UFUNC_E; }
".var" { return K_VAR; }
".var/cobj" { return K_VAR_COBJECT; }

View File

@ -1016,6 +1016,11 @@ for example to store the return value for a real function, use
"%ret/real 0;". It is up to the function caller to set up the argument
references.
* %retload/vec4 <index>
Read a value from the indexed function argument. The value is read
from the argument and pushed to the appropriate stack.
* %set/dar/obj/real <index>
* %set/dar/obj/str <index>
* %set/dar/obj/vec4 <index>

View File

@ -95,7 +95,7 @@ static struct __vpiModPath*modpath_dst = 0;
%token K_RESOLV K_SCOPE K_SFUNC K_SFUNC_E K_SHIFTL K_SHIFTR K_SHIFTRS
%token K_SUBSTITUTE
%token K_THREAD K_TIMESCALE K_TRAN K_TRANIF0 K_TRANIF1 K_TRANVP
%token K_UFUNC_REAL K_UFUNC_E K_UDP K_UDP_C K_UDP_S
%token K_UFUNC_REAL K_UFUNC_VEC4 K_UFUNC_E K_UDP K_UDP_C K_UDP_S
%token K_VAR K_VAR_COBJECT K_VAR_DARRAY
%token K_VAR_QUEUE
%token K_VAR_S K_VAR_STR K_VAR_I K_VAR_R K_VAR_2S K_VAR_2U
@ -257,6 +257,9 @@ statement
| T_LABEL K_UFUNC_REAL T_SYMBOL ',' T_NUMBER ',' symbols '(' symbols ')' T_SYMBOL ';'
{ compile_ufunc_real($1, $3, $5, $7.cnt, $7.vect, $9.cnt, $9.vect, $11, 0); }
| T_LABEL K_UFUNC_VEC4 T_SYMBOL ',' T_NUMBER ',' symbols '(' symbols ')' T_SYMBOL ';'
{ compile_ufunc_vec4($1, $3, $5, $7.cnt, $7.vect, $9.cnt, $9.vect, $11, 0); }
| T_LABEL K_UFUNC_E T_SYMBOL ',' T_NUMBER ',' T_SYMBOL ',' symbols '(' symbols ')' T_SYMBOL ';'
{ compile_ufunc_vec4($1, $3, $5, $9.cnt, $9.vect, $11.cnt, $11.vect, $13, $7); }

View File

@ -38,6 +38,30 @@
#include <windows.h>
#endif
class ufunc_real : public ufunc_core {
public:
ufunc_real(unsigned ow, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t start_address,
__vpiScope*call_scope,
char*scope_label);
~ufunc_real();
void finish_thread();
};
class ufunc_vec4 : public ufunc_core {
public:
ufunc_vec4(unsigned ow, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t start_address,
__vpiScope*call_scope,
char*scope_label);
~ufunc_vec4();
void finish_thread();
};
ufunc_core::ufunc_core(unsigned owid, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t sa, __vpiScope*call_scope__,
@ -114,6 +138,18 @@ void ufunc_core::finish_thread_real_()
thread_ = 0;
}
void ufunc_core::finish_thread_vec4_()
{
assert(thread_);
vvp_vector4_t val = vthread_get_vec4_stack(thread_, 0);
vthread_pop_vec4(thread_, 1);
propagate_vec4(val);
thread_ = 0;
}
/*
* This method is only called when a trigger event occurs. Just arrange for
* the function to be called.
@ -147,6 +183,24 @@ void ufunc_core::invoke_thread_()
}
}
ufunc_vec4::ufunc_vec4(unsigned ow, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t start_address,
__vpiScope*call_scope,
char*scope_label)
: ufunc_core(ow, ptr, nports, ports, start_address, call_scope, scope_label)
{
}
ufunc_vec4::~ufunc_vec4()
{
}
void ufunc_vec4::finish_thread()
{
finish_thread_vec4_();
}
ufunc_real::ufunc_real(unsigned ow, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t start_address,
@ -195,8 +249,8 @@ void compile_ufunc_real(char*label, char*code, unsigned wid,
last instruction is the usual %end. So the thread looks
like this:
%exec_ufunc <core>;
%reap_ufunc <core>;
%exec_ufunc/real <core>;
%reap_ufunc;
%end;
The %exec_ufunc copies the input values into local regs
@ -251,8 +305,70 @@ void compile_ufunc_vec4(char*label, char*code, unsigned wid,
unsigned portc, struct symb_s*portv,
char*scope_label, char*trigger_label)
{
// XXXX NOT IMPLEMENTED
assert(0);
/* The input argument list and port list must have the same
sizes, since internally we will be mapping the inputs list
to the ports list. */
assert(argc == portc);
__vpiScope*call_scope = vpip_peek_current_scope();
assert(call_scope);
/* Construct some phantom code that is the thread of the
function call. The first instruction, at the start_address
of the function, loads the ports and calls the function.
The second instruction collects the function result. The
last instruction is the usual %end. So the thread looks
like this:
%exec_ufunc/vec4 <core>;
%reap_ufunc;
%end;
The %exec_ufunc copies the input values into local regs
and runs the function code. The %reap_ufunc then copies
the output value to the destination net functor. */
vvp_code_t exec_code = codespace_allocate();
exec_code->opcode = of_EXEC_UFUNC_VEC4;
code_label_lookup(exec_code, code, false);
vvp_code_t reap_code = codespace_allocate();
reap_code->opcode = of_REAP_UFUNC;
vvp_code_t end_code = codespace_allocate();
end_code->opcode = &of_END;
/* Run through the function ports (which are related to but
not the same as the input ports) and arrange for their
binding. */
vvp_net_t**ports = new vvp_net_t*[portc];
for (unsigned idx = 0 ; idx < portc ; idx += 1) {
functor_ref_lookup(&ports[idx], portv[idx].text);
}
/* Create the output functor and attach it to the label. Tell
it about the start address of the code stub, and the scope
that will contain the execution. */
vvp_net_t*ptr = new vvp_net_t;
ufunc_core*fcore = new ufunc_vec4(wid, ptr, portc, ports,
exec_code, call_scope,
scope_label);
ptr->fun = fcore;
define_functor_symbol(label, ptr);
free(label);
exec_code->ufunc_core_ptr = fcore;
reap_code->ufunc_core_ptr = fcore;
wide_inputs_connect(fcore, argc, argv);
/* If this function has a trigger event, connect the functor to
that event. */
if (trigger_label)
input_connect(ptr, 0, trigger_label);
free(argv);
free(portv);
}
#ifdef CHECK_WITH_VALGRIND
static map<ufunc_core*, bool> ufunc_map;

View File

@ -70,6 +70,7 @@ class ufunc_core : public vvp_wide_fun_core {
protected:
void finish_thread_real_();
void finish_thread_vec4_();
private:
void recv_vec4_from_inputs(unsigned port);
@ -94,16 +95,4 @@ class ufunc_core : public vvp_wide_fun_core {
vvp_code_t code_;
};
class ufunc_real : public ufunc_core {
public:
ufunc_real(unsigned ow, vvp_net_t*ptr,
unsigned nports, vvp_net_t**ports,
vvp_code_t start_address,
__vpiScope*call_scope,
char*scope_label);
~ufunc_real();
void finish_thread();
};
#endif /* IVL_ufunc_H */

View File

@ -115,6 +115,7 @@ struct vthread_s {
// corresponding stack. This is how the %ret/* instructions
// get at parent thread arguments.
vector<unsigned> args_real;
vector<unsigned> args_vec4;
private:
vector<vvp_vector4_t>stack_vec4_;
@ -143,6 +144,12 @@ struct vthread_s {
assert(use_index >= 1);
return stack_vec4_[use_index-1];
}
inline void poke_vec4(unsigned depth, const vvp_vector4_t&val)
{
assert(depth < stack_vec4_.size());
unsigned use_index = stack_vec4_.size()-1-depth;
stack_vec4_[use_index] = val;
}
inline void pop_vec4(unsigned cnt)
{
while (cnt > 0) {
@ -1348,9 +1355,13 @@ bool of_CALLF_STR(vthread_t thr, vvp_code_t cp)
bool of_CALLF_VEC4(vthread_t thr, vvp_code_t cp)
{
vthread_t child = vthread_new(cp->cptr2, cp->scope);
return do_callf_void(thr, child);
// XXXX NOT IMPLEMENTED
// This is the return value. Push a place-holder value. The function
// will replace this with the actual value using a %ret/real instruction.
thr->push_vec4(vvp_vector4_t());
child->args_vec4.push_back(0);
return do_callf_void(thr, child);
}
bool of_CALLF_VOID(vthread_t thr, vvp_code_t cp)
@ -4964,6 +4975,47 @@ bool of_RET_REAL(vthread_t thr, vvp_code_t cp)
return true;
}
/*
* %ret/vec4 <index>
*/
bool of_RET_VEC4(vthread_t thr, vvp_code_t cp)
{
size_t index = cp->number;
vvp_vector4_t val = thr->pop_vec4();
assert(index >= 0 && index < thr->args_vec4.size());
unsigned depth = thr->args_vec4[index];
// Use the depth to put the value into the stack of
// the parent thread.
thr->parent->poke_vec4(depth, val);
return true;
}
/*
* %retload/real <index>
*/
bool of_RETLOAD_REAL(vthread_t thr, vvp_code_t cp)
{
// NOT IMPLEMENTED
assert(0);
return true;
}
/*
* %retload/vec4 <index>
*/
bool of_RETLOAD_VEC4(vthread_t thr, vvp_code_t cp)
{
size_t index = cp->number;
assert(index >= 0 && index < thr->args_vec4.size());
unsigned depth = thr->args_vec4[index];
// Use the depth to put the value into the stack of
// the parent thread.
thr->push_vec4(thr->parent->peek_vec4(depth));
return true;
}
bool of_SCOPY(vthread_t thr, vvp_code_t)
{
vvp_object_t tmp;
@ -5971,7 +6023,20 @@ bool of_EXEC_UFUNC_REAL(vthread_t thr, vvp_code_t cp)
/* Create a temporary thread and run it immediately. */
vthread_t child = vthread_new(cp->cptr, child_scope);
thr->push_real(0.0);
child->args_real.push_back(0.0);
child->args_real.push_back(0);
return do_exec_ufunc(thr, cp, child);
}
bool of_EXEC_UFUNC_VEC4(vthread_t thr, vvp_code_t cp)
{
__vpiScope*child_scope = cp->ufunc_core_ptr->func_scope();
assert(child_scope);
/* Create a temporary thread and run it immediately. */
vthread_t child = vthread_new(cp->cptr, child_scope);
thr->push_vec4(vvp_vector4_t());
child->args_vec4.push_back(0);
return do_exec_ufunc(thr, cp, child);
}