Static Introspection in SystemVerilog

A recent thread in Verification Guild asked if introspection is possible in SystemVerilog. The answer is that a very limited form is possible. However, using VPI (i.e. writing C/C++ code) a much deeper access to a simulation is possible.

But what about from within the language itself? Using DPI it is possible to call functions with C linkage directly from your SystemVerilog source code. The VPI is simply a C API, so it should be possible to call these functions.

It turns out that only a subset are callable, because DPI supports only a limited set of datatypes.

Example

Here's some code that calls vpi from SV:
// file: sv_introspect.sv

module top();

reg a;
reg b;

endmodule

program main();

`include "vpi_user.svh"

initial
begin

  vpiHandle module_iter;
  vpiHandle module_obj;

  // iterate all the module
  module_iter = vpi_iterate( vpiModule, null );

  // look for the module named "top"
  while (module_iter)
  begin
      module_obj = vpi_scan( module_iter );
      if (module_obj)
      begin
          string module_name;
          module_name = vpi_get_str( vpiName, module_obj );
          $display( "module name, expect 'top': ", module_name );
          if (module_name == "top")
          begin
              vpi_free_object(module_iter);
              module_iter = null;
          end
      end
      else
      begin
          module_iter = null;
      end
  end

  if (module_obj)
  begin
      vpiHandle reg_iter;
      vpiHandle reg_obj;
      string reg_name;

      // in the "top" module there are two registers.
      // The first is named "a"
      reg_iter = vpi_iterate( vpiReg, module_obj );
      reg_obj = vpi_scan( reg_iter );
      reg_name = vpi_get_str( vpiName, reg_obj );
      $display( "reg name: expect 'a': ", reg_name );

      // The second is named "b"
      reg_obj = vpi_scan( reg_iter );
      reg_name = vpi_get_str( vpiName, reg_obj );
      $display( "reg name: expect 'b': ", reg_name );

  end
  else
  begin
    $display( "FAILED to find module named 'top'" );
  end

end

endprogram
        
This code looks for a module named "top", and prints the first two registers in it. Using vcs, you can build and run this using:
vcs -R -sverilog +vpi +acc sv_introspect.sv
        
"+vpi" enables access to the vpi routines; "+acc" enables visibility into the netlist from vpi.

The Guts

The magic is in the line
`include "vpi_user.svh"
        
This is a file that I created from the standard
vpi_user.h
header file. It's minimal content for this example is:
// file: vpi_user.svh

typedef int vpiProperty_t;
typedef chandle vpiHandle;

const vpiProperty_t vpiName = 2;
const vpiProperty_t vpiReg = 48;
const vpiProperty_t vpiModule = 32;

import "DPI" function bit vpi_free_object( vpiHandle handle );
import "DPI" function string vpi_get_str( vpiProperty_t obj_type, vpiHandle object );
import "DPI" function vpiHandle vpi_iterate( vpiProperty_t obj_type, vpiHandle reference );
import "DPI" function vpiHandle vpi_scan( vpiHandle iterator );
        

Generating a Complete vpi_user.svh

Of course, this minimal header file is insufficient for more general usage. However, it's simple to create a more complete header file, by extracting the properties from the standard vpi_user.h (and sv_vpi_user.h) header files:
#!/usr/bin/env perl

# file: extract_vpi_properties.pl

use strict;
use warnings;

unless (@ARGV)
{
    @ARGV = "$ENV{VCS_HOME}/include";
}

if ($ARGV[0] eq "-help")
{
    print "usage: $0 -help\n";
    print "usage: $0    : extrace vpi_user.h and sv_vpi_user.h from this dir (or include dir under it\n";
    print "usage: $0               : implies \$VCS_HOME\n";
    print "usage: $0 {files}       : process these files\n";
    exit 0;
}

my @files;
for my $path (@ARGV)
{
    if (-f $path)
    {
        push @files, $path;
    }
    elsif (-d $path)
    {
        for my $file ( "vpi_user.h", "sv_vpi_user.h" )
        {
            if (-f "$path/$file") { push @files, "$path/$file" }
            if (-f "$path/include/$file") { push @files, "$path/include/$file" }
        }
    }
}

my %seen;
my @enums;

for my $file (@files)
{
    open my $fh, "<", $file or die "Can't read file: $file";
    while (<$fh>)
    {
        /\#define \s+ (vpi\w+) \s+ (\d+)/x or next;
        my ($name, $value) = ($1,$2);
        next if $seen{$name}++;
        push @enums, [$name, $value];
    }
}

print <<"__EOT__";

typedef int vpiProperty_t;
typedef chandle vpiHandle;

@{[ join "\n", map { sprintf "const vpiProperty_t %s = %s;", @$_ } @enums ]}

import "DPI" function bit vpi_compare_objects( vpiHandle object1, vpiHandle object2 );
import "DPI" function bit vpi_flush( );
import "DPI" function bit vpi_free_object( vpiHandle handle );
import "DPI" function int vpi_get( vpiProperty_t obj_type, vpiHandle object );
import "DPI" function string vpi_get_str( vpiProperty_t obj_type, vpiHandle object );
import "DPI" function chandle vpi_get_userdata( vpiHandle tfcall );
import "DPI" function vpiHandle vpi_handle( vpiProperty_t obj_type, vpiHandle reference );
import "DPI" function vpiHandle vpi_handle_by_index( vpiHandle parent, int index );
import "DPI" function vpiHandle vpi_handle_by_name( string name, vpiHandle scope );
import "DPI" function vpiHandle vpi_handle_multi( vpiProperty_t obj_type, vpiHandle reference1, vpiHandle reference2 );
import "DPI" function vpiHandle vpi_iterate( vpiProperty_t obj_type, vpiHandle reference );
import "DPI" function int vpi_put_userdata( vpiHandle tfcall, chandle data );
import "DPI" function vpiHandle vpi_scan( vpiHandle iterator );

__EOT__
        

The Future

So this is what we can do for introspection today. As I noted above, it is only possible to access static netlist information without adding additional wrapper functions to hide the data structures used in vpi to express values and times (i.e. t_vpi_value, t_vpi_time, etc.). Also, it is not possible to register a task/function as a callback. I would like to think that the DPI could be upgraded in a future version of the SystemVerilog standard to allow all VPI functions to be used directly from SystemVerilog. This would allow us to leverage existing technology to enhance the functionality of the lanaguage, instead of requiring us to add additional complexity to support instrospection with new language features added specfically for that purpose. Feel free to use/enhance any of the above code. Treat it as you would any other GPL code.
Back to my main DV page