Icarus Verilog
No edit summary
 
No edit summary
Line 87: Line 87:
 
This simple command makes the "hello.vpi" module with
 
This simple command makes the "hello.vpi" module with
 
minimum fuss.
 
minimum fuss.
  +
  +
== A Worked Example ==
   
 
Let us try a complete, working example. Place the C
 
Let us try a complete, working example. Place the C

Revision as of 21:25, 19 October 2006

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, 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.

How It Works

The VPI modules are compiled loadable object code that the runtime loads at the user's request. There is a special module, the system.vpi module, that is always loaded to provide the core system tasks. Beyond that, the user tells vvp to locate and load other 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.

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 is the address of a null terminated table of functions. 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[])() = {
   hello_register,
   0
};

Note that the \texttt{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 way to compile a module is with a 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)
{
      return 0;
}

static int hello_calltf(char*user_data)
{
      vpi_printf("Hello, World!\n");
      return 0;
}

void hello_register()
{
      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[])() = {
    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.