In this chapter we are going to take a quick tour to get ourselves acquainted with Programming Language Interface (PLI) of Verilog HDL. We will go through the essentials of PLI without getting bogged down by too much of details. Our objective of this chapter would be to acquire a workable knowledge of PLI so that we can start writing basic programs. To achieve this, we will take an example of the very basic PLI activity - reading the value of a register from the design database. Once we have this we further modify it to do some other basic things using PLI. At the end, we will review what we have covered in this chapter and how it forms the basis for moving to the details of PLI. WHAT IS A PLI ? Programming Language Interface (PLI) is a way to provide Application Program Interface (API) to Verilog HDL. Essentially it is a mechanism to invoke a C function from a Verilog code. The construct which invokes a PLI routine in a Verilog code is usually called a system call. The common system calls built-in to most Verilog simulators are $display, $monitor, $finish etc. WHY PLI IS REQUIRED As the above examples of built-in PLI routines indicate, PLI is primarily used for doing such things which would not have been possible otherwise using Verilog syntax. For example, while Verilog has a way of doing file write ($fwrite, which is another built-in system call), it does not have any such construct for reading a register value directly from a file. There are other examples toowhere PLI is the only way of achieving the desired results. Some of them are:
Traditionally, the main body of a PLI code is written in a file, which is called veriuser.c by convention. Though this name is completely user-defined, but while making a script for the compilation in Verilog XL, the vconfig tool will assume this name by default (We will discuss about this later in this chapter). For our discussion, we will assume that our PLI routine will be saved in the file veriuser.c. To illustrate the basic steps for creating a PLI routine, the following problem is taken. This problem is much simplified compared to the actual 'real life' problems solved using PLI. However, this example shows all the basic steps that are required in all PLI routines. Problem description
As an example, a verilog program like this: module my_module;
Should produce the values 10 and 3 at times 100 and 300. The following are the steps for writing a PLI routine. Step 1: Including the header files The file veriuser.c containing the main PLIC program must have #include <veriuser.h>
in Verilog XL and #include <vcsuser.h> in VCS. As always in C, a header file within corner brackets (i.e. '<' and'>') means during the compilation the file should be either in the /usr/include directory or in a path include_path_name. In the later case, however, the program must be compiled with -Iinclude_path_name option. The include files can also be specified as shown below. #include "include_path_name/veriuser.h"
A typical example of include_path_name may look like /usr/caetools/verilog/.../include
Step 2 : Function prototype and Variable declaration This part of the program contains all local variables and the functions that it invokes as part of the system call. In the present case, as explained in the next step, it will be as shown below. int my_calltf(), my_checktf(); However, if the functions are in separate files, they sould be declared as external functions. extern int my_calltf(), my_checktf(); A typical PLI code, like any other C program, may need few other housekeeping variables. Step 3: The essential data structure There are a number of data structures that must be defined in a PLI
program. These are the variables through which Verilog communicates with
the C code. Although OVI (Open Verilog International - the organization
responsible for standardizing Verilog) recommends only one mandatory datastructure
(veriusertfs), the exact number
and syntax of these data structures vary from simulator to simulator. Verilog-XL
from Cadence Design Systems, for example, requires four such data structures
and functions for any PLI routine to work, whereas VCS, from Chronologic
needs none, but needs a separate input file. Going by the popularity ofVerilog
XL, we are going to mention all four of them here; the users of Chronologic
VCS and Veriwell may ignore the them. These data structures, in the order
they should appear, are given below.
char *veriuser_version_str ="\n\
endofcompile_routines
int (*endofcompile_routines[])() ={0}; err_intercept
bool err_intercept(level, facility,code)
veriusertfs
The number of rows in veriusertfs is same as that of the number of user-defined system calls, plus one for the last entry, which is a mandatory {0} . In the present case, the veriusertfs array should look like: s_tfcell veriusertfs[] =
In the above data-structure my_checktf and my_calltf are the names ofthe two functions that we will use to implement the system call $print_reg. my_checktf is the routine which checks the validity of the passed parametersetc and my_calltf actually executes the system call. These names are arbitrary and could have been something else. The first entry usertask indicates that the system call does not return anything and is equivalent to procedure in Pascal or function returning void in C. Note that :
$print_reg check=my_checktf call=my_calltf
If you have liked this tutorial so far, guess what's waiting for you in the book. Click here to order.
Step 4: tf routines The checktf routine
int my_checktf() {
The functions starting with tf_ are the library functions and commonly known as utility routines. (There is another set of library routines which start with acc_ and known as access routines. However, since in the present case only utility routines would be used, we will postpone the discussion of access routines to a later chapter) The library functions that we have used in the above function and their usages are given below.
The calltf routine
int my_calltf(){
The library functions used above and their usages are given below.
For convenience, let us see the whole program at one place. #include <veriuser.h>
int my_checktf() {
void my_calltf(){
COMPILING AND LINKING A PLI ROUTINE When the Verilog simulator encounters a user defined system call, in order to make it understand the existance of the call and properly execute it, the PLI routine has to be compiled and then bind together with the existing binary of the simulator. Though the integration of the PLI code with the verilog binary can be done manually by running c-compiler and merging the object files, it is usually done through a script for convenience. In Verilog XL, a program called vconfig generates this scripts, whose name, by default, is cr_vlog. The program vconfig asks the name of the compiled verilog that you prefer. It also asks whether to include model libraries (which themselves are PLI code) from standard vendors. At the end it asks for the path for your veriuser.c file(s). Once the script cr_vlog is generated, just by running it one can get the new Verilog simulator binary which would recognize the user defined system call. RESULTS OF RUNNING THE PLI ROUTINE A sample run of the compiled Verilog having our PLI routine producesthe following output for Verilog-XL simulator. =============================
Self-test :
GETTING THE SIMULATION TIME Although the PLI routine in the previous example worked as expected, from the result one can see there is one serious fault - from the result there is no way to figure out the simulation time when the register changed the value. With multiple invocation of this routine (as in this case) the problem becomes even worse. This can be solved using another utility function tf_gettime()which tells the current time of the simulation. This has been done in a new revision of our calltf routine below by making minor modification tothe existing code. Also for convenience, the value of the register is displayed in hexadecimal rather than decimal. void my_calltf(){
As shown in the routine, tf_gettime() returns the current simulation time and it does not need any input parameter. A sample run of the modified version of our routine give the following output. =============================
MODIFYING THE CONTENT OF A REGISTER If reading the design information is one reason why PLI is used, the other reason would be to modify these informations in the design database. The following example shows how it is done. Problem description
There are more than one ways of getting the inverted value. The algorithm described below may not be the smartest as far as doing one's complementis concerned, but it will give more insight into the PLI mechanism and library functions. Read the content of the register is as a string;
The calltf function
int invert_calltf() {
The description and the usage of the library functions used above aregiven below.
In general, the content of a variable, whose type is tf_readwrite, canbe modified. This is checked in the checktf function my_checktf(). Once tf_strgetp() reads the content in binary format ( 'h' or 'o' would have read it in hexadecimal or octal format ) it is copied to a string val_string. move() is a function yet to be defined, which would replace each occurance
of the first parameter (a character) by the second parameter (another character)
in the third parameter (a string). Once 0s are converted to1s, to distinguish
between these inverted 0s and original 1s in the string, at first all 1s
are changed to 2 - an invalid value for binary logic, but a work around
for us. At the end, all 2s
The program completes its task by putting back the inverted valueback into the register using tf_strdelputp() function. Here is the code for move(). void move(from, to, in_str)
The complete code for the entire PLI routine is given below. Note thatthis time it is written for the VCS simulator. #include "vcsuser.h"
int my_checktf() {
int invert_calltf() {
/* Step 1 of the algorithm */
/* Step 2 of the algorithm */
/* Step 3 of the algorithm */
void move(from, to, in_str)
For VCS, the equivalent of veriusertfs[] structure is given in a file pli.tab whose content in this case is given below. $invert call=invert_calltf check=my_checktf RUNNING AGAIN A sample Verilog program containing the call $invert is shown below. module mymodule;
Assuming the code is saved in the file test.v, it is compiled for VCS to generate the executable binary pli_invert by giving the followingcommand. $ vcs -o pli_invert -P pli.tab test.vveriuser.c Executing pli_invert, we get the following result. Contains Chronologic Simulation proprietary information. Oct 21 00:05 1997 $invert: Modifying the content from00001111
to 11110000 at time 100
Self-test :
SUMMARY In this chapter, we have learnt the basic structure of a PLI routine and mechanism of linking it to build a custom version of Verilog. We have also learnt how to read, convert and modify the values of elementary components of a design database in Verilog through PLI C code. And also how to read the simulation time. These are the basic and often the most needed tasks of a PLI code. Compared to the rest of the universe that PLI offers, however, this is really a small part. In later chapters we will see how PLI can be used to access other design information not related to the basic component --name of the current module and its parent, or controlling the path-delays, for example. Also we have so far used only the so-called 'utility routines' providedby PLI1.0. There is a complete different set of routines, known as'access routines', which is often recommended for more uniform interfacing.We will learn about it in Chapter 4.
All product names mentioned here are are registered trademarks of their respective owner companies. |