Sunday, April 10, 2016

Reflection is a mechanism that allows "inspection of classes, interfaces, fields and methods at runtime without knowing the names of the interfaces, fields, methods at compile time. It also allows instantiation of new objects and invocation of methods". In e, using reflection together with define as compute macros allows us to do some really cool stuff.

A major complaint about SystemVerilog is that it lacks reflection capabilities. These could be useful for writing super generic code,but the main use I can see, though, is in testing. For example, in Java, the JUnit framework decides which methods to execute as unit tests based on annotations (@Test). Reflection could also allow a user to write some very generic checks, for example checking that after a method call the rand_mode() attribute of all variables of an object is set to 0. Mocking frameworks also use a lot of reflection to do their thing (I hear), though I don't really know the exact details.

Verilog and SystemVerilog provide the Verilog Programming Interface (VPI), which the IEEE 1800-2012 standard describes as a part of the "procedural interface that allows foreign language functions to access the internal data structures of a SystemVerilog simulation". It is defined as a C programming language interface to be used for writing C applications. Unfortunately, these functions aren't directly available in a SystemVerilog package. I've no idea why, since this is pretty low hanging fruit. (This isn't 100% true, since it is possible to use the VPI to "enhance" the simulator by defining new system tasks and functions, but the process is very cumbersome.)

I've used the VPI for some past projects. It's written in an object oriented style, but since it's plain old C code it doesn't have the same feel as a proper OOP language. This makes it not quite so comfortable to use. It is pretty powerful, though, and it allows a developer to mine a lot of information out of the compiled SystemVerilog code. On the other side of the HVL barricade, e's reflection API is excellent. It's every bit as powerful and very comfortable to use, making it a worthy reference.

Our goal in this post is to define a reflection API for SystemVerilog. Anything we develop should have a nice object oriented interface, with classes to model each language construct (i.e. variables, functions, tasks, classes, etc.). The most reasonable (and only) course of action is to leverage the existing VPI, by building an adaption layer.

The first thing we need to do is to find a way to access the VPI routines which are available in C from code written in SystemVerilog. Fortunately, this is possible via the Direct Programming Interface (DPI), which allows us to call code written in other programming languages. The interface to C code (called DPI-C) is thoroughly defined in the standard. We can use it write a package that imports the VPI functions into SystemVerilog. Apparently Dave Rich already beat me to the punch here (by a couple of years judging from the code comments), with his DVCon 2016 paper, Introspection into SystemVerilog without Turning It Inside Out, where he presents this exact idea. I had already created my own repository on GitHub, vpi, before reading his paper, so we'll be using that.

The VPI is fully documented in Annexes K, L and M of the IEEE 1800-2012 standard. These sections contain the header files that simulators must provide for VPI applications to include. In this post we're mostly interested in the parts related to classes and variables. For the next sections, I'll assume that readers are already familiar with the VPI. If you're new to the topic, you should give Sections 36, 37 (especially) and 38 of the LRM a quick read before you continue.

The first step is to mirror the type definitions on the C side into SystemVerilog. The DPI-C allows defines a rich set of equivalent type mappings from one language to the other, so this step is pretty straightforward:

typedefintPLI_INT32;typedeflongintPLI_INT64;typedefchandlevpiHandle;

The basic thing we can do with a vpiHandle is to get certain properties it has. The properties simulation objects can have are defined in the VPI headers and also need to be mirrored into SystemVerilog:

I've used parameters inside the package for them, but they could just as well have been constants. I'm not really sure what would have been better here (though now on second glance I'm leaning toward constants), so if you have some input here, I'd love to hear it.

Simulation objects are typically "connected" to each other via references. To traverse one-to-one relationships (e.g. a class has a class definition), we need to define the corresponding object types:

It can also be the case that one simulation objects contains references to multiple other simulation objects of the same type (e.g. a class definition contains multiple variables). For traversing one-to-many relationships we need to define the appropriate values to pass to vpi_iterate(...):

parametervpiVariables=100;

Once we've set up our types and our other constants, we can import the VPI functions using the DPI-C:

I've tried to use the same names for the arguments as in the LRM, but some of them are SystemVerilog keywords. In these cases I added an underscore as a suffix.

This didn't work directly when trying to compile the code. The SystemVerilog compiler complained that it couldn't find the vpi_*(...) functions anywhere. I guess this has something to do with the fact that when compiling it doesn't link the VPI binaries. What I tried to do is to define a dummy C file that just includes the VPI header:

#include "vpi_user.h"

I hope that this way I could force the linking to happen. This didn't really help. I think this is because, since we don't use any of the functions declared in the header, the C compiler assumes that we don't need them at all. After some searching online, I tried to find out if it's possible to force the compiler/linker to link unused symbols. This is what my search pointed me to:

PLI_BYTE8*(*vpi_get_str__)(PLI_INT32,vpiHandle)=&vpi_get_str;

I'm not a C expert, so don't quote me on this, but I think the code above should declare a function pointer with the respective return and argument types called vpi_get_str__(...) that points to the original vpi_get(...) function. Then, in our import code, instead of importing vpi_get_str(...), we would import vpi_get_str__(...). This also didn't work.

The only thing that did work was to define vpi_get_str__(...) as a wrapper function that calls the real vpi_get_str(...):

Instead of importing the original vpi_*(...) functions I imported the wrappers, which worked perfectly. I guess this is because the C compiler was happy when it saw the VPI functions getting used. While writing the post, though, I noticed that I didn't do this for vpi_handle(...), which I imported directly. This whole process of writing wrappers and importing them is pretty tedious, so if anyone has any idea what's going on here and how the imports could be streamlined, I'd very much appreciate it.

The code above is going to figure out that the root module is called test and tells us that there are no more root modules. This isn't particularly impressive, but the important thing to note is that it's written exclusively in SystemVerilog.

Now that we've got the power of the VPI at our fingertips, it's time to start thinking about how to implement our reflection API. All reflection operations in e are handled by the rf_managerstruct. This is as good a name as any other for the top level entity that I can think of at the moment. We want our rf_manager to be able to give us an object that contains all of the information about a certain class. This object will be of type rf_class. As a first step, we want to get the corresponding rf_class object based on the class name:

The code above has some limitations. It only works for classes defined in packages and if the class name is used in multiple packages, it's going to stop at the first one it finds. This is something that's going to get fixed later, but now we're at the proof-of-concept phase.

I've made the get_class_by_name(...) static inside rf_manager, but a better idea might have been to make rf_manager a singleton and leave the function non-static. This way I could later implement state inside the class, which would be useful, for example, to cache calls to reflection methods (since I'm assuming VPI calls don't come cheap). I'm curious what you're thoughts are here.

What could we want to know about a class? For starters, we'd like to know its name and what variables are declared inside it:

As you can see, once we know what information we want to extract, getting it is just a matter of consulting the VPI object models and performing the required traversal operations. Since this is a pretty laborious task, having a cleaner API based on object orientation can certainly help make things more usable.

Let's look at a simple example of the reflection API in action. Let's take a simple class definition:

We've been calling our new package a reflection API, but what we've implemented so far is just introspection of the code structure (i.e. compile time aspects). Aside from implementing all relevant queries, to truly say that we've implemented introspection, we have to be able to get the values of variables from different objects (i.e. run time aspects). Once we have this, we'll be able to officially claim that we've implemented reflection when we're also able to set variable values. These steps will be coming soon, so stay tuned.

Experience shows that VPI support, particularly for SystemVerilog constructs, varies from vendor to vendor and strongly depends on how new the simulator version being used is. To check if the package can be used with a particular tool, I've written an "acceptance" test suite. It's based on SVUnit and it tries to check if all functionality is available. It's pretty light at the moment and it supports only one simulator, but it's a step in the right direction.

You can find the reflection package on GitHub. I'll be adding functionality to it when it's needed. As I've already said a couple of times throughout the post, I'm open to any ideas or contributions that you may have, so don't hesitate to use the comment section below or to get in touch with me via the contact page.

About

I am a Verification Engineer at Infineon Technologies, where I get the chance to work with both e and SystemVerilog.
I started the Verification Gentleman blog to store solutions to small (and big) problems I've faced in my day to day work. I want to share them with the community in the hope that they may be useful to someone else.