Chapter 13. Programming Language Interface

Verilog provides the set of standard system tasks and functions defined in Appendix C, List of Keywords, System Tasks, and Compiler Directives. However, designers frequently need to customize the capability of the Verilog language by defining their own system tasks and functions. To do this, the designers need to interact with the internal representation of the design and the simulation environment in the Verilog simulator. The Programming Language Interface (PLI) provides a set of interface routines to read internal data representation, write to internal data representation, and extract information about the simulation environment. User-defined system tasks and functions can be created with this predefined set of PLI interface routines.

Verilog Programming Language Interface is a very broad area of study. Thus, only the basics of Verilog PLI are covered in this chapter. Designers should consult the IEEE Standard Verilog Hardware Description Language document for complete details of the PLI.

There are three generations of the Verilog PLI.

  1. Task/Function (tf_) routines make up the first generation PLI. These routines are primarily used for operations involving user-defined task/function arguments, utility functions, callback mechanism, and writing data to output devices.

  2. Access (acc_) routines make up the second-generation PLI. These routines are provide object-oriented access directly into a Verilog HDL structural description. These routines can be used to access and modify a wide variety of objects in the Verilog HDL description.

  3. Verilog Procedural Interface (vpi_) routines make up the third-generation PLI. These routines are a superset of the functionality of acc_ and tf_ routines.

For the sake of simplicity, we will discuss only acc_ and tf_ routines in this chapter.

Learning Objectives

  • Explain how PLI routines are used in a Verilog simulation.

  • Describe the uses of the PLI.

  • Define user-defined system tasks and functions and user-defined C routines.

  • Understand linking and invocation of user-defined system tasks.

  • Explain how the PLI is represented conceptually inside a Verilog simulator.

  • Identify and describe how to use the two classes of PLI library routines: access routines and utility routines.

  • Learn to create user-defined system tasks and functions and use them in simulation.

The first step is to understand how PLI tasks fit into the Verilog simulation. A sample simulation flow using PLI routines is shown in Figure 13-1.

PLI Interface

Figure 13-1. PLI Interface

A designer describes the design and stimulus by using standard Verilog constructs and system tasks. In addition, user-defined system tasks can also be invoked in the design and stimulus. The design and stimulus are compiled and converted to an internal design representation. The internal design representation is typically in the Verilog simulator proprietary format and is incomprehensible to the designer. The internal representation is then used to run the actual simulation and produce output.

Each of the user-defined system tasks is linked to a user-defined C routine. The C routines are described by means of a standard library of PLI interface routines, which can access the internal design representation, and the standard C routines available with the C compiler. The standard PLI library is provided with the Verilog simulator. A list of PLI library routines is provided in Appendix B, List of PLI Routines. The PLI interface allows the user to do the following:

  • Read internal data structures

  • Modify internal data structures

  • Access simulation environment

Without the PLI interface, the designer would have to understand the format of the internal design representation to access it. PLI provides a layer of abstraction that allows access to internal data structures through an interface that is uniform for all simulators. The user-defined system tasks will work even if the internal design representation format of the Verilog simulator is changed or if a new Verilog simulator is used.

Uses of PLI

PLI provides a powerful capability to extend the Verilog language by allowing users to define their own utilities to access the internal design representation. PLI has various applications.

  • PLI can be used to define additional system tasks and functions. Typical examples are monitoring tasks, stimulus tasks, debugging tasks, and complex operations that cannot be implemented with standard Verilog constructs.

  • Application software like translators and delay calculators can be written with PLI.

  • PLI can be used to extract design information such as hierarchy, connectivity, fanout, and number of logic elements of a certain type.

  • PLI can be used to write special-purpose or customized output display routines. Waveform viewers can use this file to generate waveforms, logic connectivity, source level browsers, and hierarchy information.

  • Routines that provide stimulus to the simulation can be written with PLI. The stimulus could be automatically generated or translated from some other form of stimulus.

  • General Verilog-based application software can be written with PLI routines. This software will work with all Verilog simulators because of the uniform access provided by the PLI interface.

Linking and Invocation of PLI Tasks

Designers can write their own user-defined system tasks by using PLI library routines. However, the Verilog simulator must know about the existence of the user-defined system task and its corresponding user-defined C function. This is done by linking the user-defined system task into the Verilog simulator.

To understand the process, let us consider the example of a simple system task $hello_verilog. When invoked, the task simply prints out a message “Hello Verilog World”. First, the C routine that implements the task must be defined with PLI library routines. The C routine hello_verilog in the file hello_verilog.c is shown below.

#include "veriuser.h" /*include the file provided in release dir */

int hello_verilog()
{
    io_printf("Hello Verilog World
");
}

The hello_verilog routine is fairly straightforward. The io_printf is a PLI library routine that works exactly like printf.

The following sections show the steps involved in defining and using the new $hello_verilog system task.

Linking PLI Tasks

Whenever the task $hello_verilog is invoked in the Verilog code, the C routine hello_verilog must be executed. The simulator needs to be aware that a new system task called $hello_verilog exists and is linked to the C routine hello_verilog. This process is called linking the PLI routines into the Verilog simulator. Different simulators provide different mechanisms to link PLI routines. Also, though the exact mechanics of the linking process might be different for simulators, the fundamentals of the linking process remain the same. For details, refer to the latest reference manuals available with your simulator.

At the end of the linking step, a special binary executable containing the new $hello_verilog system task is created. For example, instead of the usual simulator binary executable, a new binary executable hverilog is produced. To simulate, run hverilog instead of your usual simulator executable file.

Invoking PLI Tasks

Once the user-defined task has been linked into the Verilog simulator, it can be invoked like any Verilog system task by the keyword $hello_verilog. A Verilog module hello_top, which calls the task $hello_verilog, is defined in file hello.v as shown below.

module hello_top;

initial
    $hello_verilog; //Invoke the user-defined task $hello_verilog

endmodule

Output of the simulation is as follows:

Hello Verilog World

General Flow of PLI Task Addition and Invocation

We discussed a simple example to illustrate how a user-defined system task is named, implemented in terms of a user-defined C routine, linked into the simulator, and invoked in the Verilog code. More complex PLI tasks discussed in the following sections will follow the same process. Figure 13-2 summarizes the general process of adding and invoking a user-defined system task.

General Flow of PLI Task Addition and Invocation

Figure 13-2. General Flow of PLI Task Addition and Invocation

Internal Data Representation

Before we understand how to use PLI library routines, it is first necessary to describe how a design is viewed internally in the simulator. Each module is viewed as a collection of object types. Object types are elements defined in Verilog, such as:

  • 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

Each object type has a corresponding set that identifies all objects of that type in the module. Sets of all object types are interconnected.

A conceptual internal representation of a module is shown in Figure 13-3.

Conceptual Internal Representation a Module

Figure 13-3. Conceptual Internal Representation a Module

Each set contains all elements of that object type in the module. All sets are interconnected. The connections between the sets are bidirectional. The entire internal representation can be traversed by using PLI library routines to obtain information about the module. PLI library routines are discussed later in the chapter.

To illustrate the internal data representation, consider the example of a simple 2-to-1 multiplexer whose gate level circuit is shown in Figure 13-4.

2-to-1 Multiplexer

Figure 13-4. 2-to-1 Multiplexer

The Verilog description of the circuit is shown in Example 13-1.

Example 13-1. Verilog Description of 2-to-1 Multiplexer

module mux2_to_1(out, i0, i1, s);

output out; //output port
input i0, i1; //input ports
input s;

wire sbar, y1, y2; //internal nets

//Gate Instantiations
not n1(sbar, s);
and a1(y1, i0, sbar);
and a2(y2, i1, s);
or o1(out, y1, y2);

endmodule

The internal data representation for the 2-to-1 multiplexer is shown in Figure 13-5. Sets are shown for primitive instances, primitive instance terminals, module ports, and nets. Other object types are not present in this module.

Internal Data Representation of 2-to-1 Multiplexer

Figure 13-5. Internal Data Representation of 2-to-1 Multiplexer

The example shown above does not contain register, integers, module instances, and other object types. If they are present in a module definition, they are also represented in terms of sets. This description is a conceptual view of the internal structures. The exact implementation of data structures is simulator dependent.

PLI Library Routines

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

Role of Access and Utility Routines

Figure 13-6. Role of Access and Utility Routines

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

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

Mechanics of access routines

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 routine acc_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;

Types of access routines

We discuss six types of access routines.

  • Handle routinesThey return handles to objects in the design. The name of handle routines always starts with the prefix acc_handle_.

  • Next routinesThey 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) routinesThey 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 routinesThey 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 routinesThey perform miscellaneous operations related to access routines. For example, acc_initialize() and acc_close() are utility routines.

  • Modify routinesThey 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.

Examples of access 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 1Get 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.

Example 13-2. PLI Routine to get Module Port List

#include "acc_user.h"

int get_ports()
{
  handle  mod, port;
  int input_ctr = 0;
  int output_ctr = 0;
  int inout_ctr = 0;

  acc_initialize();

  mod = acc_handle_tfarg(1); /* get a handle to the module instance
                                first argument in the system task argument
                                list */

  port = acc_handle_port(mod, 0); /* get the first port of the module */

  while( port != null ) /* loop for all ports */
  {
    if (acc_fetch_direction(port) == accInput) /* Input port */
    {
        io_printf("Input Port %s 
", acc_fetch_fullname(port));
                                            /* full hierarchical name */

        input_ctr++;
    }
    else if (acc_fetch_direction(port) == accOutput) /* Output port */
    {
        io_printf("Output Port %s 
", acc_fetch_fullname(port));
        output_ctr++;
    }
    else if (acc_fetch_direction(port) == accInout) /* Inout port */
 {
        io_printf("Inout Port %s 
", acc_fetch_fullname(port));
        inout_ctr++;
    }

    port = acc_next_port(mod, port); /* go to the next port */
  }

  io_printf("Input Ports = %d Output Ports = %d, Inout ports = %d

",
                        input_ctr, output_ctr, inout_ctr);
  acc_close();

}

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*/

initial
begin
  $get_ports("top.my_mux"); /*invoke task $get_ports to get port list*/
end

endmodule

Invocation 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.out
Input Port top.my_mux.i0
Input Port top.my_mux.i1
Input Port top.my_mux.s
Input Ports = 3 Output Ports = 1, Inout ports = 0

Example 2Monitor 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.

Example 13-3. PLI Routine to Monitor Nets for Value Changes

#include "acc_user.h"

char convert_to_char();
int display_net();

int my_monitor()
{
  handle net;
  char *netname ; /*pointer to store names of nets*/
  char *malloc();

  acc_initialize(); /*initialize environment*/

  net = acc_handle_tfarg(1); /*get a handle to the net to be monitored*/

  /*Find hierarchical name of net and store it*/
  netname = malloc(strlen(acc_fetch_fullname(net)));
  strcpy(netname,  acc_fetch_fullname(net));

  /* Call the VCL routine to add a signal to the monitoring list*/
  /* Pass four arguments to acc_vcl_add task*/
  /* 1st : handle to the monitored object (net)
     2nd : Consumer C routine to call when the object value changes (display_net)
     3rd : String to be passed to consumer C routine (netname)
     4th : Predefined VCL flags: vcl_verilog_logic for logic monitoring
                          vcl_verilog_strength for strength monitoring */
  acc_vcl_add(net, display_net, netname, vcl_verilog_logic);

  acc_close();
}

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_net and 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.

Example 13-4. Consumer Routine for VCL Example

/*Consumer routine. Called whenever any monitored net changes*/
display_net(vc_record)
p_vc_record vc_record; /*Structure p_vc_record predefined in
                          acc_user.h*/
{

  /*Print time, name, and new value of the changed net */
  io_printf("%d New value of net %s is %c 
",
                  vc_record->vc_lowtime,
                  vc_record->user_data,
                  convert_to_char(vc_record->out_value.logic_value));
}

/*Miscellaneous routine to convert predefined character constant to
  ASCII character*/
char convert_to_char(logic_val)
char logic_val;
{
  char temp;

  switch(logic_val)
  {
    /*vcl0, vcl1, vclX and vclZ are predefined in acc_user.h*/
    case vcl0: temp='0';
               break;
    case vcl1: temp='1';
               break;
    case vclX: temp='X';
               break;
    case vclZ: temp='Z';
               break;
  }
  return(temp);
}

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_1

initial //Add nets to the monitoring list
begin
  $my_monitor("top.my_mux.sbar");
  $my_monitor("top.my_mux.y1");
end

initial //Apply Stimulus
begin
  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;
end

endmodule

The output of the simulation is shown below.

0 New value of net top.my_mux.y1 is 0
0 New value of net top.my_mux.sbar is 1
5 New value of net top.my_mux.y1 is 1
5 New value of net top.my_mux.sbar is 0
5 New value of net top.my_mux.y1 is 0
10 New value of net top.my_mux.sbar is X
15 New value of net top.my_mux.y1 is X
15 New value of net top.my_mux.sbar is 0
15 New value of net top.my_mux.y1 is 0

Utility Routines

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

Mechanics of utility 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"

Types of utility routines

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.

Example of utility routines

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.

Table 13-1. Specifications for $my_stop_finish

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.

Example 13-5. User C Routine my_stop_finish, Using Utility Routines

#include "veriuser.h"

int my_stop_finish()
{

  if(tf_nump() == 1) /* if 1 argument is passed to the my_stop_finish
                        task, display only simulation time and message*/
  {
    if(tf_getp(1) == 0) /* get value of argument. If the argument
                           is 0, then stop the simulation*/
    {
      io_printf("Mymessage: Simulation stopped at time %d
",
                                    tf_gettime());
      tf_dostop(); /*stop the simulation*/
    }
    else if(tf_getp(1) == 1)  /* if the argument is 0 then terminate
                           the simulation*/
    {
      io_printf("Mymessage: Simulation finished at time %d
",
                                    tf_gettime());
      tf_dofinish(); /*terminate the simulation*/
    }
    else
      /* Pass warning message */
      tf_warning("Bad arguments to $my_stop_finish at time %d
",
                                                    tf_gettime());
  }

  else if(tf_nump() == 2) /* if 1 argument is passed to the my_stop_finish
                        task, then print module instance from which the
                        task was called, time and message */
  {
    if(tf_getp(1) == 0) /* if the argument is 0 then stop
                           the simulation*/
    {
      io_printf
       ("Mymessage: Simulation stopped at time %d in instance  %s 
",
                                    tf_gettime(), tf_mipname());
      tf_dostop(); /*stop the simulation*/
    }
    else if(tf_getp(1) == 1)  /* if the argument is 0 then terminate
                           the simulation*/
    {
      io_printf
       ("Mymessage: Simulation finished at time %d in instance  %s 
",
                                    tf_gettime(), tf_mipname());
      tf_dofinish(); /*terminate the simulation*/
    }
    else
      /* Pass warning message */
      tf_warning("Bad arguments to $my_stop_finish at time %d
",
                                                    tf_gettime());
  }

}

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_1

initial //Apply Stimulus
begin
  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
                          //name
end

endmodule

The output of the simulation with a Verilog simulator is shown below.

Mymessage: Simulation stopped at time 0
Type ? for help
C1 > .
Mymessage: Simulation stopped at time 5 in instance  top
C1 > .
"my_stop_finish.v", 14: warning! Bad arguments to $my_stop_finish at time 10

Mymessage: Simulation finished at time 15 in instance  top

Summary

In this chapter, we described the Programming Language Interface (PLI) for Verilog. The following aspects were discussed:

  • PLI Interface provides a set of C interface routines to read, write, and extract information about the internal data structures of the design. Designers can write their own system tasks to do various useful functions.

  • PLI Interface can be used for monitors, debuggers, translators, delay calculators, automatic stimulus generators, dump file generators, and other useful utilities.

  • A user-defined system task is implemented with a corresponding user-defined C routine. The C routine uses PLI library calls.

  • The process of informing the simulator that a new user-defined system task is attached to a corresponding user C routine is called linking. Different simulators handle the linking process differently.

  • User-defined system tasks are invoked like standard Verilog system tasks, e.g., $hello_verilog(); . The corresponding user C routine hello_verilog is executed whenever the task is invoked.

  • A design is represented internally in a Verilog simulator as a big data structure with sets for objects. PLI library routines allow access to the internal data structures.

  • Access (acc) routines and utility (tf) routines are two types of PLI library routines.

  • Utility routines represent the first generation of Verilog PLI. Utility routines are used to pass data back and forth across the boundary of user C routines and the original Verilog design. Utility routines start with the prefix tf_. Utility routines do not interact with object handles.

  • Access routines represent the second generation of Verilog PLI. Access routines can read and write information about a particular object from/to the design. Access routines start with the prefix acc_. Access routines are used primarily across the boundary of user C routines and internal data representation. Access routines interact with object handles.

  • Value change link (VCL) is a special category of access routines that allow monitoring of objects in a design. A consumer routine is executed whenever the monitored object value changes.

  • Verilog Procedural Interface (VPI) routines represent the third generation of Verilog PLI. VPI routines provide a superset of the functionality of acc_ and tf_ routines. VPI routines are not covered in this book.

Programming Language Interface is a very broad area of study. Thus, only the basics of Verilog PLI are covered in this chapter. Designers should consult the IEEE Standard Verilog Hardware Description Language document for details of PLI.

Exercises

Refer to Appendix B, List of PLI Routines and IEEE Standard Verilog Hardware Description Language document, for a list of PLI access and utility routines, their function, and usage. You will need to use some PLI library calls that were not discussed in this chapter.

1:

Write a user-defined system task, $get_in_ports, that gets full hierarchical names of only the input ports of a module instance. Hierarchical module instance name is the input to the task (Hint: Use the C routine in Example 13-2 as a reference). Link the task into the Verilog simulator. Find the input ports of the 1-bit full adder defined in Example 5-7 on page 75.

2:

Write a user-defined system task, $count_and_gates, which counts the number of and gate primitives in a module instance. Hierarchical module instance name is the input to the task. Use this task to count the number of and gates in the 4-to-1 multiplexer in Example 5-5.

3:

Create a user-defined system task, $monitor_mod_output, that finds out all the output signals of a module instance and adds them to a monitoring list. The line “Output signal has changed” should appear whenever any output signal of the module changes value. (Hint: Use VCL routines.) Use the 2-to-1 multiplexer in Example 13-1. Add output signals to the monitoring list by using $monitor_mod_output. Check results by applying stimulus.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.212.145