PLI library routines provide a standard interface to the internal data representation of the design. The user-defined C routines for user-defined system tasks are written by using PLI library routines. In the example in Section 13.2, Linking and Invocation of PLI Tasks, $hello_verilog is the user-defined system task, hello_verilog is the user-defined C routine, andio_printf is a PLI library routine.
There are two broad classes of PLI library routines: access routines and utility routines. (Note that vpi_ routines are a superset of access and utility routines and are not discussed in this book.)
Access routines provide access to information about the internal data representation; they allow the user C routine to traverse the data structure and extract information about the design. Utility routines are mainly used for passing data across the Verilog/Programming Language Boundary and for miscellaneous housekeeping functions. Figure 13-6 shows the role of access and utility routines in PLI.
A complete list of PLI library routines is provided in Appendix B, List of PLI Routines. The function and usage of each routine are also specified.
Access routines are also popularly called acc routines. Access routines can do the following:
Read information about a particular object from the internal data representation
Write information about a particular object into the internal data representation
We will discuss only reading of information from the design. Information about modifying internal design representation can be found in the Programming Language Interface (PLI) Manual. However, reading of information is adequate for most practical purposes.
Access routines can read information about objects in the design. Objects can be one of the following types:
Module instances, module ports, module pin-to-pin paths, and intermodule paths
Top-level modules
Primitive instances, primitive terminals
Nets, registers, parameters, specparams
Integer, time, and real variables
Timing checks
Named events
Some observations about access routines are listed below.
Access routines always start with the prefix acc_.
A user-defined C routine that uses access routines must first initialize the environment by calling the routineacc_initialize(). When exiting, the user-defined C routine must call acc_close().
If access routines are being used in a file, the header file acc_user.h must also be included. All access routine data types and constants are predefined in the file acc_user.h.
#include "acc_user.h"Access routines use the concept of a handle to access an object. Handles are predefined data types that point to specific objects in the design. Any information about the object can be obtained once the object handle is obtained. This is similar to the concept of file handles for accessing files in C programs. An object handle identifier is declared with the keyword handle.
handle top_handle;We discuss six types of access routines.
Handle routines. They return handles to objects in the design. The name of handle routines always starts with the prefix acc_handle_.
Next routines. They return the handle to the next object in the set of a given object type in a design. Next routines always start with the prefix acc_next_ and accept reference objects as arguments.
Value Change Link (VCL) routines. They allow the user system task to add and delete objects from the list of objects that are monitored for value changes. VCL routines always begin with the prefix acc_vcl_ and do not return a value.
Fetch routines. They can extract a variety of information about objects. Information such as full hierarchical path name, relative name, and other attributes can be obtained. Fetch routines always start with the prefix acc_fetch_.
Utility access routines. They perform miscellaneous operations related to access routines. For example,acc_initialize() and acc_close() are utility routines.
Modify routines. They can modify internal data structures. We do not discuss them in this book. Refer to the IEEE Standard Verilog Hardware Description Language document for details about modify routines.
A complete list of access routines and their usage is provided in Appendix B, List of PLI Routines.
We will discuss two examples that illustrate the use of access routines. The first example is a user-defined system task to find names of all ports in a module and count the number of ports. The second example is a user-defined system task that monitors the changes in values of nets.
Example 1: Get Module Port List
Let us write a user-defined system task $get_ports to find complete hierarchical names of input, output, and inout ports in a module and to count the number of input, output, and inout ports. The user-defined system task will be invoked in Verilog as $get_ports("<hierarchical_module_name>"). The user-defined C routine get_ports, which implements the task$get_ports, is described in file get_ports.c. The file get_ports.c is shown in Example 13-3.
Notice that handle, fetch, next, and utility access routines are used to write the user C routine.
Link the new task into the Verilog simulator as described in Section 13.2.1, Linking PLI Tasks. To check the newly defined task, we will use it to find out the port list of the module mux2_to_1 described in Example 13-1. A top-level module that instantiates the 2-to-1 multiplexer and invokes the $get_ports task is shown below.
module top;wire OUT;reg I0, I1, S;mux2_to_1 my_mux(OUT, I0, I1, S); /*Instantiate the 2-to-1 mux*/initialbegin $get_ports("top.my_mux"); /*invoke task $get_ports to get port list*/endendmoduleInvocation of $get_ports causes the user C routine $get_ports to be executed. The output of the simulation is shown below.
Output Port top.my_mux.outInput Port top.my_mux.i0Input Port top.my_mux.i1Input Port top.my_mux.sInput Ports = 3 Output Ports = 1, Inout ports = 0Example 2: Monitor Nets for Value Changes
This example highlights the use of Value Change Link (VCL) routines. Instead of using the $monitor task provided with the Verilog simulator, let us define our own task to monitor specific nets in the design for value changes. The task$my_monitor("<net_name>"); is to be invoked to add a <net_name> to the monitoring list.
The user-defined C routine my_monitor, which implements the user-defined system task, is shown in Example 13-3.
Notice that the net is added to the monitoring list with the routine acc_vcl_add. A consumer routine display_net is an argument to acc_vcl_add. Whenever the value of the net changes, the acc_vcl_add calls the consumer routine display_netand passes a pointer to a data structure of the type p_vc_record. A consumer routine is a C routine that performs an action determined by the user whenever acc_vcl_add calls it. The p_vc_record is predefined in the acc_user.h file, as shown below.
typedef struct t_vc_record{ int vc_reason; /*reason for value change*/ int vc_hightime; /*Higher 32 bits of 64-bit simulation time*/ int vc_lowtime; /*Lower 32 bits of 64-bit simulation time*/ char *user_data; /*String passed in 3rd argument of acc_vcl_add*/ union { /*New value of the monitored signal*/ unsigned char logic_value; double real_value; handle vector_handle; s_strengths strengths_s; } out_value;} *p_vc_record;The consumer routine display_net simply displays the time of change, name of net, and new value of the net. The consumer routine is written as shown in Example 13-4. Another routine, convert_to_char, is defined to convert the logic value constants to an ASCII character.
Link the new task into the Verilog simulator as described in Section 13.2.1, Linking PLI Tasks. To check the newly defined task, we will use it to monitor nets sbar and y1 when stimulus is applied to module mux2_to_1 described in Example 13-1 on page 281. A top-level module that instantiates the 2-to-1 multiplexer, applies stimulus, and invokes the $my_monitor task is shown below.
module top;wire OUT;reg I0, I1, S;mux2_to_1 my_mux(OUT, I0, I1, S); //Instantiate the module mux2_to_1initial //Add nets to the monitoring listbegin $my_monitor("top.my_mux.sbar"); $my_monitor("top.my_mux.y1");endinitial //Apply Stimulusbegin I0=1'b0; I1=1'b1; S = 1'b0; #5 I0=1'b1; I1=1'b1; S = 1'b1; #5 I0=1'b0; I1=1'b1; S = 1'bx; #5 I0=1'b1; I1=1'b1; S = 1'b1;endendmoduleThe output of the simulation is shown below.
0 New value of net top.my_mux.y1 is 00 New value of net top.my_mux.sbar is 15 New value of net top.my_mux.y1 is 15 New value of net top.my_mux.sbar is 05 New value of net top.my_mux.y1 is 010 New value of net top.my_mux.sbar is X15 New value of net top.my_mux.y1 is X15 New value of net top.my_mux.sbar is 015 New value of net top.my_mux.y1 is 0Utility routines are miscellaneous PLI routines that pass data in both directions across the Verilog/user C routine boundary. Utility routines are also popularly called "tf" routines.
Some observations about utility routines are listed below.
Utility routines always start with the prefix tf_.
If utility routines are being used in a file, the header file veriuser.h must be included. All utility routine data types and constants are predefined in veriuser.h.
#include "veriuser.h"Utility routines are available for the following purposes:
Get information about the Verilog system task invocation
Get argument list information
Get values of arguments
Pass back new values of arguments to calling system task
Monitor changes in values of arguments
Get information about simulation time and scheduled events
Perform housekeeping tasks, such as saving work areas, and storing pointers to tasks
Do long arithmetic
Display messages
Halt, terminate, save, and restore simulation
A list of utility routines, their function, and usage is provided in Appendix B.
Until now we encountered only one utility routine, io_printf(). Now we will look at a few more utility routines that allow passing of data between the Verilog design and the user-defined C routines.
Verilog provides the system tasks $stop and $finish that suspend and terminate the simulation. Let us define our own system task, $my_stop_finish, which does both stopping and finishing based on the arguments passed to it. The complete specifications for the user-defined system task $my_stop_finish are shown in Table 13-1.
1st Argument
2nd Argument
Action
0
none
Stop simulation. Display simulation time and message.
1
none
Finish simulation. Display simulation time and message.
0
any value
Stop simulation. Display simulation time, module instance from which stop was called, and message.
1
any value
Finish simulation. Display simulation time, module instance from which stop was called, and message.
The source code for the user-defined C routine my_stop_finish is shown in Example 13-5.
Link the new task into the Verilog simulator as described in Section 13.2.1, Linking PLI Tasks. To check the newly defined task $my_stop_finish, a stimulus in which $my_stop_finish is called with all possible combinations of arguments is applied to the module mux2_to_1 described in Example 13-1 on page 281. A top-level module that instantiates the 2-to-1 multiplexer, applies stimulus, and invokes the $my_stop_finish task is shown below.
module top;wire OUT;reg I0, I1, S;mux2_to_1 my_mux(OUT, I0, I1, S); //Instantiate the module mux2_to_1initial //Apply Stimulusbegin I0=1'b0; I1=1'b1; S = 1'b0; $my_stop_finish(0); //Stop simulation. Don't print module instance name #5 I0=1'b1; I1=1'b1; S = 1'b1; $my_stop_finish(0,1); //Stop simulation. Print module instance name #5 I0=1'b0; I1=1'b1; S = 1'bx; $my_stop_finish(2,1); //Pass bad argument 2 to the task #5 I0=1'b1; I1=1'b1; S = 1'b1; $my_stop_finish(1,1); //Terminate simulation. Print module instance //nameendendmoduleThe output of the simulation with a Verilog simulator is shown below.
Mymessage: Simulation stopped at time 0Type ? for helpC1 > .Mymessage: Simulation stopped at time 5 in instance topC1 > ."my_stop_finish.v", 14: warning! Bad arguments to $my_stop_finish at time 10Mymessage: Simulation finished at time 15 in instance top