Functional Features

The main functions accomplished by ACC subroutines are:

  • Reading information about specific objects from internal data structures
  • Writing information about specific objects into internal data structures

The object types that ACC subroutines can operate on are:

  • Module instances, module ports, module end-to-end paths, and paths between modules
  • Top-level modules
  • Primitive instances and primitive ports
  • Variable types such as wire, reg, parameter, integer, time, real, etc.
  • Timing checks
  • Named events

The main characteristics of ACC subroutines are:

  • All are prefixed with acc_
  • At the beginning, acc_initialize() must be called to initialize the environment
  • At exit, acc_close() must be called
  • When using functions from the ACC library, the header file acc_user.h must be included
  • The concept of handles is used to access objects. A handle is a predefined data type pointing to a specific object in the design. Once a handle is obtained, all information about the object can be accessed. This is similar to the file handle concept in C language. Handles are declared with the keyword handle.

The main types of ACC subroutines are:

  • Handle subroutines (handle): return a handle to an object in the design, always prefixed with acc_handle_.
  • Next subroutines (next): return a handle to the next object in a specific set of objects in the design, always prefixed with acc_next_, and take the referenced object as a parameter.
  • Value Change Link (VCL) subroutines: can add and remove objects from the list of objects monitored for value changes, always prefixed with acc_vcl_, and have no return value.
  • Fetch subroutines (fetch): can extract attribute information such as hierarchical paths, always prefixed with acc_fetch_.
  • Miscellaneous subroutines (miscellaneous): used to perform various miscellaneous operations related to access subroutines. For example, acc_initialize() and acc_close() are both miscellaneous subroutines.
  • Modify subroutines (modify): can modify internal data structures.

For the complete list of ACC subroutines and their brief usage instructions, refer to the next section"8.5 ACC Subroutine List"。

ACC Subroutine Examples

Below are usage examples for only some ACC subroutines, mainly illustrating the basic process of designing Verilog system tasks using ACC subroutines.

Design Requirements

This time, design a system task that monitors a counter.

On one hand, the system task monitors the value of a counter in Verilog; on the other hand, after detecting the counting clock in Verilog, it also performs software counting and compares the software and hardware count values to verify the correctness of the hardware logic. At the same time, when the counter counts to a specific value, it outputs the values of some signal variables.

This design approach is also a common scenario for PLI interfaces. Software implements a logic function, called CModel. Then hardware also implements the same logic function using Verilog. Through the PLI interface program, the software and hardware programs can execute simultaneously and compare results to verify the correctness of the hardware logic design.

Design Analysis

In the PLI interface part, a Value Change Link subroutine is used to monitor the clock. As soon as a rising edge of the clock arrives, a software function is called to perform software counting and compare the software and hardware count values.

Software Design

The software design code is as follows, with details explained in the comments. Save it to the file monitor_gyc.c.

Example

#include "acc_user.h"

handle  hand_rstn, hand_clk, hand_cnt, hand_cout ;
int cnt_soft  = 0 ;   //Value of the software counter
int cout_soft = 0 ;   //Overflow bit of the software counter

//Software counting and comparison function
void act_monitor(){
    p_acc_value value ; //Declare the structure variable defined in ACC
    //Read the value of a signal variable in Verilog, string type
    char *rstn_rtl_str = acc_fetch_value(hand_rstn, "%d", value);
    char *clk_rtl_str  = acc_fetch_value(hand_clk, "%d", value);
    char *cnt_rtl_str  = acc_fetch_value(hand_cnt, "%d", value);
    char *cout_rtl_str = acc_fetch_value(hand_cout, "%d", value);

    //Convert string to integer
    int rstn_rtl       = atoi(rstn_rtl_str) ;
    int clk_rtl        = atoi(clk_rtl_str) ;
    int cnt_rtl        = atoi(cnt_rtl_str) ;
    int cout_rtl       = atoi(cout_rtl_str) ;

    //Software counter
    //Reset
    if (!rstn_rtl) {
        cnt_soft    = 0 ;
        cout_soft   = 0 ;
        io_printf("---iii--- Reset state! \n");
    }
    //Normal operation
    else if (rstn_rtl && clk_rtl) {
        if (cnt_soft == 9) {
            cnt_soft    = 0 ;    //Count 10
        }
        else {
            cnt_soft    = cnt_soft + 1 ; //Count
        }
    }
    cout_soft = cnt_soft == 9 ? 1 : 0 ; //Carry

    //Compare software and hardware counts when the clock is low; this is a relatively safe time
    if (!clk_rtl && rstn_rtl) {
        if ((cnt_soft != cnt_rtl) || (cout_soft != cout_rtl)) {
            io_printf("--Err--- rtl cnt: %d, and soft cnt: %d\n", cnt_rtl, cnt_soft);
            io_printf("--Err--- rtl cout: %d, and soft cout: %d\n", cout_rtl, cout_soft);
        }
        //If there is no error in the software and hardware count comparison, output the state values of the signals when one counting cycle is completed
        else if (cnt_soft == 9)  {
            io_printf("--Monitor--- rtl and soft cnt: %d\n", cnt_soft);
            io_printf("--Monitor--- rtl and soft cout: %d\n", cout_soft);
        }
    }
}

//PLI interface, monitoring clock changes
void my_monitor() {
    acc_initialize(); //Initialization is required
    //Get the handle variable of the first parameter of the system task my_monitor
    hand_rstn   = acc_handle_tfarg(1);
    hand_clk    = acc_handle_tfarg(2);
    hand_cnt    = acc_handle_tfarg(3);
    hand_cout   = acc_handle_tfarg(4);
    //"#define VCL_VERILOG_LOGIC 2" in acc_user.h
    //Indicates the VCL callback mechanism shall report
    //Once hand_clk changes, call the act_monitor function
    acc_vcl_add(hand_clk, act_monitor, null, vcl_verilog_logic);
    acc_close(); //Close the task
}

Hardware Design

The Verilog description of a decimal counter is as follows. Save it to the file counter10.v.

Example

module counter10(
   input                rstn,
   input                clk,
   output [3:0]         cnt,
   output               cout);

   reg [3:0]            cnt_temp ;
   always@(posedge clk or negedge rstn) begin
      if (! rstn) begin
         cnt_temp        <= 4'b0 ;
      end
      else if (cnt_temp==4'd9) begin
         cnt_temp        <=4'b000;
      end
      else begin
         cnt_temp        <= cnt_temp + 1'b1 ;
      end
   end

   assign  cout = (cnt_temp==4'd9) ;
   assign  cnt  = cnt_temp ;

endmodule

testbench

The testbench description is as follows. Save it to the file test.v.

Example

`timescale 1ns/1ps
module test ;
   reg          rstn, clk ;
   wire [3:0]   cnt ;
   wire         cout ;

   initial begin
      rstn      = 0 ;
      clk       = 0 ;
      #19.8;
      rstn      = 1 ;
   end
   always #5 clk = ~clk ;

   counter10 u_cnt(
     .rstn      (rstn),
     .clk       (clk),
     .cnt       (cnt),
     .cout      (cout));

   initial begin
      $my_monitor(test.rstn, test.clk, test.cnt, test.cout);
   end

   initial begin
      forever begin
         #100;
         if ($time >= 300)  $finish ;
      end
   end
endmodule

Compile and Simulation

Under Linux, use the following command to compile monitor_gyc.c and output the monitor_gyc.o file. Pay attention to the relative path.

gcc -I ${VCS_HOME}/include -c ../tb/monitor_gyc.c

When compiling with VCS, you need to create a link file that VCS can recognize. Name the file pli_gyc.tab, with the following content.

$my_monitor is the name of the system task called in Verilog;

call=my_monitor means calling the function my_monitor() in the software C program;

acc=rw:* sets the attributes of the system task. rw indicates that internal data can be read and written. After the colon, the module or signal region that the system task acts on can be declared. ":*" means it can act on all modules.

$my_monitor call=my_monitor acc=rw:*

Add the following parameter line when compiling with VCS.

-P ../tb/pli_gyc.tab

The simulation result log is as follows.

From the log, it can be seen that the software and hardware count comparison is correct, and the status after the counter completes one cycle of counting is also normal.

Chapter Source Code Download

Download