Hello,
Recently, I started working on a Nim wrapper for another SystemVerilog/C interface called "VPI".
The SystemVerilog standard provides a vpi_user.h that has this:
/****************************** GLOBAL VARIABLES ******************************/
PLI_VEXTERN PLI_DLLESPEC void (*vlog_startup_routines[]) ();
I am not familiar with this C syntax, but looking at a C example using this header file (see below), it looks like this is a pointer to an array of function symbols, specifically functions with no inputs and no return type.
C example:
// Register the new system task here
void (*vlog_startup_routines[ ] ) () = {
registerHelloSystfs,
0 // last entry must be 0
};
How can I map that vlog_startup_routines declaration and its example use to Nim?
Thanks!
it's an array of function pointers, rather than a pointer to an array also looks like it's null-terminated (!)
the naive translation of the type would be UncheckedArray[proc(){.cdecl.}] but that doesn't compile, of course.
this works, but requires the user to null-terminate their array properly, i'm not sure how you 'append' a nil to an array. macro?
template set_vlog_startup_routines*[S:static int](ps:array[S,proc(){.nimcall.}]) =
var vlog_startup_routines{.inject,exportc.} = ps
Thanks!
I took your example and embellished it lightly.
# https://forum.nim-lang.org/t/7945#50584
# User code must call this template at global scope, and only once!
template setVlogStartupRoutines*(procArray: varargs[proc() {.nimcall.}]) =
const
numProcs = procArray.len
var vlog_startup_routines {.inject, exportc, dynlib.}: array[numProcs + 1, proc() {.nimcall.}]
for i in 0 ..< numProcs:
vlog_startup_routines[i] = procArray[i]
vlog_startup_routines[numProcs] = nil
I cannot yet fully verify this as I need to map rest of the functions and structs from that example.
With this at least compiling, I think the battle is half won. I will update soon :)
@shirleyquirk Sorry, but I have a followup question related to C as I am trying to translate that to Nim.
In that C example, I see
typedef struct t_vpi_systf_data {
PLI_INT32 type; // vpiSysTask, vpiSysFunc
PLI_INT32 sysfunctype; // vpiSysTask, vpi[Int,Real,Time,Sized, SizedSigned]Func
PLI_BYTE8 *tfname; // First character must be `$'
PLI_INT32 (*calltf)(PLI_BYTE8 *);
PLI_INT32 (*compiletf)(PLI_BYTE8 *);
PLI_INT32 (*sizetf)(PLI_BYTE8 *); // For sized function callbacks only
PLI_BYTE8 *user_data;
} s_vpi_systf_data, *p_vpi_systf_data;
If we focus on PLI_INT32 (*calltf)(PLI_BYTE8 *); bit, that's a pointer to a function accepting a char array pointer (cstring) and returning an int, right?
But then I see this:
void hello() {
vpi_printf("\n\nHello!!\n\n");
}
void registerHelloSystfs() {
s_vpi_systf_data task_data_s;
p_vpi_systf_data task_data_p = &task_data_s;
task_data_p->type = vpiSysTask;
task_data_p->tfname = "$hello";
task_data_p->calltf = hello;
task_data_p->compiletf = 0;
vpi_register_systf(task_data_p);
}
... task_data_p->calltf = hello; Here, hello is not a function that's accepting a cstring.
The whole example actually runs fine in C, so I am confused why.
@shirleyquirk I just updated that hello proc's type in Nim guided by compilation errors and it all worked!!!
What that commit does is define a hello proc in Nim and that's called as $hello from SystemVerilog, and when that SystemVerilog simulation is run, I see:
xcelium> run
Hello!!
Simulation complete via $finish(1) at time 0 FS + 1
./tb.sv:7 $finish;
xcelium> exit
Many thanks for your help :)
that's a 32-bit pointer to a function accepting a char array pointer (cstring) and void return, right?
almost, same weirdness again with the way C defines its function pointers. it goes
returntype (*function_name)(argtypes...)
so that one's a
type s_vpi_sys_data = object
...
calltf:proc(ptr PLI_BYTE8):PLI_INT32{.cdecl.}
...
again, i hate cdecl as it does nothing but make type errors (again, except on win32, where .nimcall is actually different) but technically it's the correct annotation.
but yes, even so that task_data_p->calltf is playing very fast and loose.
with gcc it does give a warning:
thing.c:27:25: warning: assignment to ‘int (*)(char *)’ from incompatible pointer type ‘void (*)()’ [-Wincompatible-pointer-types]
But yeah, it compiles and runs. ::shrugs in C::
the only way it wouldn't work is maybe on win32 fastcall (one of the few callee-cleanup calling conventions), if the caller expected to be sending >2 arguments and so pushed some on to the stack which the callee (not expecting any) then didn't clean up. but i digress.
you should not do that, that's poor example code, and Nim will not allow you to do that.