
Using VPI
=========

Icarus Verilog implements a portion of the PLI 2.0 API to Verilog. This allows
programmers to write C code that interfaces with Verilog simulations to
perform tasks otherwise impractical with straight Verilog. Many Verilog
designers, especially those who only use Verilog as a synthesis tool, can
safely ignore the entire matter of the PLI (and this chapter) but the designer
who wishes to interface a simulation with the outside world cannot escape VPI.

The rest of this article assumes some knowledge of C programming, Verilog PLI,
and of the compiler on your system. In most cases, Icarus Verilog assumes the
GNU Compilation System is the compiler you are using, so tips and instructions
that follow reflect that. If you are not a C programmer, or are not planning
any VPI modules, you can skip this entire article. There are references at the
bottom for information about more general topics.

How It Works
------------

The VPI modules are compiled loadable object code that the runtime loads at
the user's request. The user commands vvp to locate and load modules with the
"-m" switch. For example, to load the "sample.vpi" module::

  % vvp -msample foo.vvp

The vvp run-time loads the modules first, before executing any of the
simulation, or even before compiling the vvp code. Part of the loading
includes invoking initialization routines. These routines register with the
run-time all the system tasks and functions that the module implements. Once
this is done, the run time loader can match names of the called system tasks
of the design with the implementations in the VPI modules.

(There is a special module, the system.vpi module, that is always loaded to
provide the core system tasks.)

The simulator run time (The "vvp" program) gets a handle on a freshly loaded
module by looking for the symbol "vlog_startup_routines" in the loaded
module. This table, provided by the module author and compiled into the
module, is a null terminated table of function pointers. The simulator calls
each of the functions in the table in order. The following simple C definition
defines a sample table::

  void (*vlog_startup_routines[])(void) = {
     hello_register,
     0
  };

Note that the "vlog_startup_routines" table is an array of function pointers,
with the last pointer a 0 to mark the end. The programmer can organize the
module to include many startup functions in this table, if desired.

The job of the startup functions that are collected in the startup table is to
declare the system tasks and functions that the module provides. A module may
implement as many tasks/functions as desired, so a module can legitimately be
called a library of system tasks and functions.

Compiling VPI Modules
---------------------

To compile and link a VPI module for use with Icarus Verilog, you must compile
all the source files of a module as if you were compiling for a DLL or shared
object. With gcc under Linux, this means compiling with the "-fpic" flag. The
module is then linked together with the vpi library like so::

  % gcc -c -fpic hello.c
  % gcc -shared -o hello.vpi hello.o -lvpi

This assumes that the "vpi_user.h header file and the libvpi.a library file
are installed on your system so that gcc may find them. This is normally the
case under Linux and UNIX systems. An easier, the preferred method that works
on all supported systems is to use the single command::

  % iverilog-vpi hello.c

The "iverilog-vpi" command takes as command arguments the source files for
your VPI module, compiles them with proper compiler flags, and links them into
a vpi module with any system specific libraries and linker flags that are
required. This simple command makes the "hello.vpi" module with minimum fuss.

A Worked Example
----------------

Let us try a complete, working example. Place the C code that follows into the
file hello.c::

  # include  <vpi_user.h>

  static int hello_compiletf(char*user_data)
  {
        (void)user_data; // Avoid a warning since user_data is not used.
        return 0;
  }

  static int hello_calltf(char*user_data)
  {
        (void)user_data; // Avoid a warning since user_data is not used.
        vpi_printf("Hello, World!\n");
        return 0;
  }

  void hello_register(void)
  {
        s_vpi_systf_data tf_data;

        tf_data.type      = vpiSysTask;
        tf_data.tfname    = "$hello";
        tf_data.calltf    = hello_calltf;
        tf_data.compiletf = hello_compiletf;
        tf_data.sizetf    = 0;
        tf_data.user_data = 0;
        vpi_register_systf(&tf_data);
  }

  void (*vlog_startup_routines[])(void) = {
      hello_register,
      0
  };

and place the Verilog code that follows into hello.v::

  module main;
    initial $hello;
  endmodule

Next, compile and execute the code with these steps::

  % iverilog-vpi hello.c
  % iverilog -ohello.vvp hello.v
  % vvp -M. -mhello hello.vvp
  Hello, World!

The compile and link in this example are conveniently combined into the
"iverilog-vpi" command. The "iverilog" command then compiles the "hello.v"
Verilog source file to the "hello.vvp" program. Next, the "vvp" command
demonstrates the use of the "-M" and "-m" flags to specify a vpi module search
directory and vpi module name. Specifically, they tell the "vvp" command where
to find the module we just compiled.

The "vvp" command, when executed as above, loads the "hello.vpi" module that
it finds in the current working directory. When the module is loaded, the
vlog_startup_routines table is scanned, and the "hello_register" function is
executed. The "hello_register" function in turn tells "vvp" about the system
tasks that are included in this module.

After the modules are all loaded, the "hello.vvp" design file is loaded and
its call to the "$hello" system task is matched up to the version declared by
the module. While "vvp" compiles the "hello.vvp" source, any calls to "$hello"
are referred to the "compiletf" function. This function is called at compile
time and can be used to check parameters to system tasks or function. It can
be left empty like this, or left out completely. The "compiletf" function can
help performance by collecting parameter checks in compile time, so they do
not need to be done each time the system task is run, thus potentially saving
execution time overall.

When the run-time executes the call to the hello system task, the
"hello_calltf" function is invoked in the loaded module, and thus the output
is generated. The "calltf" function is called at run time when the Verilog
code actually executes the system task. This is where the active code of the
task belongs.

System Function Return Types
----------------------------

Icarus Verilog supports system functions as well as system tasks, but there is
a complication. Notice how the module that you compile is only loaded by the
"vvp" program. This is mostly not an issue, but elaboration of expressions
needs to keep track of types, so the main compiler needs to know the return
type of functions.

Starting with Icarus Verilog v11, the solution is quite simple. The names and
locations of the user's VPI modules can be passed to the compiler via the
"iverilog" -m and -L flags and the IVERILOG_VPI_MODULE_PATH environment
variable. The compiler will load and analyse the specified modules to
automatically determine any function return types. The compiler will also
automatically pass the names and locations of the specified modules to the
"vvp" program, so that they don't need to be specified again on the "vvp"
command line.

For Icarus Verilog versions prior to v11, the solution requires that the
developer of a module include the table in a form that the compiler can
read. The System Function Table file carries this information. A simple
example looks like this::

  # Example sft declarations of some common functions
  $random      vpiSysFuncInt
  $bitstoreal  vpiSysFuncReal
  $realtobits  vpiSysFuncSized 64 unsigned

This demonstrates the format of the file and support types. Each line contains
a comment (starts with "#") or a type declaration for a single function. The
declaration starts with the name of the system function (including the leading
"$") and ends with the type. The supported types are:

* vpiSysFuncInt
* vpiSysFuncReal
* vpiSysFuncSized <wid> <signed|unsigned>

Any functions that do not have an explicit type declaration in an SFT file are
implicitly taken to be "vpiSysFuncSized 32 unsigned".

The module author provides, along with the ".vpi" file that is the module, a
".sft" that declares all the function return types. For example, if the file
is named "example.sft", pass it to the "iverilog" command line or in the
command file exactly as if it were an ordinary source file.

Cadence PLI Modules
-------------------

With the cadpli module, Icarus Verilog is able to load PLI1 applications that
were compiled and linked to be dynamic loaded by Verilog-XL or
NC-Verilog. This allows Icarus Verilog users to run third-party modules that
were compiled to interface with XL or NC. Obviously, this only works on the
operating system that the PLI application was compiled to run on. For example,
a Linux module can only be loaded and run under Linux. In addition, a 64-bit
version of vvp can only load 64-bit PLI1 applications, etc.

Icarus Verilog uses an interface module, the "cadpli" module, to connect the
worlds. This module is installed with Icarus Verilog, and is invoked by the
usual -m flag to iverilog or vvp. This module in turn scans the extended
arguments, looking for -cadpli= arguments. The latter specify the share object
and bootstrap function for running the module. For example, to run the module
product.so, that has the bootstrap function "my_boot"::

  % vvp -mcadpli a.out -cadpli=./product.so:my_boot

The "-mcadpli" argument causes vvp to load the cadpli.vpl library module. This
activates the -cadpli= argument interpreter. The -cadpli=<module>:<boot_func>
argument, then, causes vvp, through the cadpli module, to load the loadable
PLI application, invoke the my_boot function to get a veriusertfs table, and
scan that table to register the system tasks and functions exported by that
object. The format of the -cadpli= extended argument is essentially the same
as the +loadpli1= argument to Verilog-XL.

The integration from this point is seamless. The PLI application hardly knows
that it is being invoked by Icarus Verilog instead of Verilog-XL, so operates
as it would otherwise.

Other References
----------------

Since the above only explains how to get PLI/VPI working with Icarus Verilog,
here are some references to material to help with the common aspects of
PLI/VPI.

* Principles of Verilog PLI by Swapnajit Mittra. ISBN 0-7923-8477-6
* The Verilog PLI Handbook by Stuart Sutherland. ISBN 0-7923-8489-X
