Openvera/Rvm To Systemverilog/Vmm Conversion: How To Avoid Death by A Thousand Cuts'
Openvera/Rvm To Systemverilog/Vmm Conversion: How To Avoid Death by A Thousand Cuts'
Openvera/Rvm To Systemverilog/Vmm Conversion: How To Avoid Death by A Thousand Cuts'
Venkata Chintapalli
Dan Steinberg
Venkata.Chintapalli@idt.com
Dan.Steinberg@idt.com
ABSTRACT
SystemVerilog (SV) has emerged as the leading Hardware Verification Language (HVL) for
verifying complex digital logic. On top of SV, is the popular Verification Methodology Manual
(VMM), which consists of methodology guidelines and a class library to simplify and
standardize the creation of verification infrastructure. Given the lure of SV, there is an
understandable desire to port existing testbenches written in other HVLs to SV. Since a large
portion of the testbench features in SV were donated from the OpenVera (OV) language, and
since VMM is based upon the earlier Reference Verification Methodology (RVM), it would
seem that porting an environment from OV/RVM to SV/VMM would be a simple endeavor. In
this paper, we set out to test this hypothesis by attempting such a conversion using proven
testbench and RTL code as a starting point.
Although OV and SV are similar, we quickly discovered that there are a multitude of syntactical
differences, and hence the feeling of "Death by a thousand cuts". For the purpose of this
exercise, and with the goal of helping others in similar situations, we have developed a
comprehensive Perl script to simplify the conversion process. In this paper, we present our
overall experience in successfully converting our testbench from OV/RVM to SV/VMM. We
describe the aspects of the conversion process that are automatically handled by our script as
well as those that must be handled manually. We also highlight potential pitfalls and
workarounds that we encountered during the conversion process.
Table of Contents
1. Introduction......................................................................................................................... 4
2. Motivation........................................................................................................................... 4
3. Project scope & tools .......................................................................................................... 4
4. Conversion details............................................................................................................... 5
4.1 Easily scriptable changes .................................................................................................... 5
4.2 Tougher-but-scriptable changes.......................................................................................... 6
4.3 Manual changes .................................................................................................................. 8
4.3.1 Shadow variables ............................................................................................................ 9
4.3.2 Default constraints .......................................................................................................... 9
4.3.3 Hierarchical references ................................................................................................... 9
4.3.4 Delay elements within functions..................................................................................... 9
4.3.5 Dynamic array dynamic_size attribute ......................................................................... 10
4.3.6 Program block............................................................................................................... 10
4.3.7 Interfaces....................................................................................................................... 10
4.3.8 Formatting with rvm_log .............................................................................................. 11
4.3.9 RVM Notification rvm_notify.wait_for_t() method ..................................................... 11
4.3.10 Miscellaneous changes ................................................................................................. 12
5. Automating the conversion process .................................................................................. 13
6. Potential Pitfalls................................................................................................................ 16
6.1.1 Complex pre-processor directives ................................................................................ 16
6.1.2 SV Function/Task arguments direction is sticky .......................................................... 17
6.1.3 Miscellaneous pitfalls ................................................................................................... 17
7. Conclusions....................................................................................................................... 17
8. References......................................................................................................................... 19
9. Appendix........................................................................................................................... 20
OpenVera (OV) is also an HVL that preceded SV. In fact, OV was donated to the committee
that developed SV, and so the two languages have very similar capabilities. VCS natively
supports OV simulations, and may be referred to as Native Test Bench (NTB).
Regardless of the type of design that is being verified, there are a lot of common tasks that need
to be performed. For this reason, the Reference Verification Methodology (RVM) was
developed by Synopsys. RVM consists of an extremely useful class library as well as a long list
of guidelines. Together, these lay the foundation for rapidly generating scalable and reusable
verification environments. The RVM framework is designed to work with OV.
The Verification Methodology Manual (VMM) was also developed by Synopsys. VMM also
consists of a class library and a set of guidelines that very much follow their RVM predecessor.
The main difference between the two is that VMM is designed to work with SV.
In this paper, we will explore what it takes to convert a testbench from OV/RVM to SV/VMM.
2. Motivation
A question that one may ask is, “Why convert an existing OV/RVM platform to SV/VMM”?
This is a reasonable question to ask, given that Synopsys continues to support the OV language
within VCS. In fact, it is our understanding that VCS supports co-simulation of OV with SV.
Accordingly, converting an existing platform in the immediate term is not necessarily critical.
When looking at the longer term view, however, it appears that such a conversion is quite
desirable. There are a number of reasons for this. The entire industry is moving to SV, and
VMM has been widely adopted. By switching over to the latest and greatest industry standards,
one can take advantage of the best-in-class tools and Verification IP (VIP). Although it’s not
necessarily the case now, in the long run SV environments should interoperate nicely among the
various tool vendors. Finally, we predict that the majority of new testbench development work
will gradually shift to SV, and OV development will eventually be phased out.
When we started planning out this task, we looked for existing tools that could help us get the
job done. Unfortunately, Synopsys doesn’t provide any such tool, although they did provide an
SV/OV language comparison document that helped. In our search, we only found one tool that
claimed to automate the conversion process. This was a commercial tool that would cost real
money. We were pretty sure that management would not be willing to pay for such a tool, so
this was deemed a non-option. Accordingly, we never evaluated this tool. Instead, we decided
that we would develop our own script to automate the conversion process, and use this script on
our own code to see how well it worked.
Given that Synopsys donated the OV language constructs to SV, and given that VMM is based
on RVM, we had hoped the conversion process would be trivial - say, something as easy as
search and replace ‘rvm’ with ‘vmm’. We decided to test out this hypothesis for ourselves.
4. Conversion details
We have categorized the types of conversions required from OV/RVM to SV/VMM into three
main categories. They are: easily scriptable changes, tougher-but-scriptable changes, and
manual changes. The types of conversions done in each of these categories are explained in
detail below. In addition, a table listing all the OV/RVM to SV/VMM conversions is listed in
the appendix. It contains a comprehensive list of conversions performed by the script and also
lists changes which are not practical to automate, and require manual modifications by the user.
Comments embedded in source code can be extremely valuable, especially when dealing with
inherited code. Source code that lacks embedded comments can lead to an enormous amount of
frustration. Accordingly, in developing our automated script, we felt it was critical to not lose
the existing comments during the conversion process. Currently, the script extracts all the
single-line (i.e., //) and multi-line (i.e., /* */) comments and saves them before performing any
conversions. The comments are transferred to the converted code in their exact location after the
conversion is complete.
#endif `endif
One of the key syntactical differences between OV and SV is in the way scopes are coded. OV
uses open and close curly braces ‘{‘ and ‘}’ for most constructs. These are used for constructs
such as classes, tasks, functions, constraints, program blocks, if-else, for loops, etc. In SV,
however, these constructs follow along the lines of the traditional Verilog syntax. Instead of
curly braces, SV employs semicolons and begin/end keywords. For example, in SV the first
open curly brace of a class definition is changed to a semicolon, and the final curly brace of the
class is changed to the endclass keyword. A colon and an optional class identifier may be placed
after the endclass keyword. In order to make these types of changes, the script has to have the
intelligence to diligently track the current scope and level of nested indentation. To make things
even trickier, SV retains the usage of curly braces for constraint and concatenation constructs, so
clearly the script cannot blindly convert curly braces to begin and end. An example of this is
shown below.
One additional comment that should be made in order to fully understand the example below is
that constructors (i.e., new() class methods) in OV may be defined as tasks, whereas in SV they
must be defined as functions. The script automatically converts such tasks to functions.
OV/RVM SV/VMM
constraint c1 { constraint c1 {
if (choose_max) { if (choose_max) {
val == max; val == max;
} }
else { else {
val <= max; val <= max;
val >= min; val >= min;
} }
} }
// Constructor // Constructor
task new () { function new ();
min = 0; min = 0;
max = 100; max = 100;
} endfunction : new
} endclass : my_xactor
OV/RVM SV/VMM
class my_xactor extends rvm_xactor { class my_xactor extends vmm_xactor;
//... //...
constraint c2 { constraint c2 {
{choose_max == 1'b1} => {val == max}; {choose_max == 1'b1} -> {val == max};
{choose_max == 1'b0} => {val in {min:(max-1)}}; {choose_max == 1'b0} -> {val inside {[min:(max-1)]}};
val !in {13,19}; !val inside {13,19};
val dist {min:/33, val dist {min:/33,
(min+1):(max-1):/33, [(min+1):(max-1)]:/33,
max:/33}; max:/33};
} }
} endclass : my_xactor
Defined macros (e.g., #define X) in OV are slightly more straight forward than in SV. In SV,
macros are defined using a backtick (`), e.g. `define X. When a defined macro is later used, a
Frequently, it’s desirable to define all macros in one file, and use them in several other files. In
this case, the order that the files are processed can become problematic. That is, if a macro is
encountered while processing one file, and later the macro definition is processed in a separate
file, this could result in the script not adding the required backtick. To avoid this problem, the
script reads in all input source files and creates a master list of all defined macros. Only then,
does the script begin to process each file in detail. In this manner, the order the files are read in
becomes irrelevant, and the conversion can be performed successfully.
Enumerated types have also changed slightly between OV and SV. In SV, in addition to adding
the typedef keyword, the enumerated name is moved to after the closing curly brace ‘}’. Another
thing that has changed slightly is the syntax of the foreach loops. Below is an example that
demonstrates the conversion of macro names, enumerated types, and the foreach construct.
OV/RVM SV/VMM
#define MAX_PKTS 100 `define MAX_PKTS 100
#define MAX_PKT_SIZE 1024 `define MAX_PKT_SIZE 1024
#define ARR_SIZE (MAX_PKTS*MAX_PKT_SIZE) `define ARR_SIZE (`MAX_PKTS*`MAX_PKT_SIZE)
task my_task (bool process_data, var integer data,) { task my_task (bool process_data, ref int data);
integer i, j, pkt_data[MAX_PKTS][MAX_PKT_SIZE]; int i, j, pkt_data[`MAX_PKTS][`MAX_PKT_SIZE];
// Process data using 'foreach' loops // Process data using 'foreach' loops
foreach (pkt_data,i,*) { foreach (pkt_data[i,]) begin
foreach (pkt_data,*,j) { foreach (pkt_data[,j]) begin
data = pkt_data[i][j]; data = pkt_data[i][j];
} end
} end
} end
} endtask : my_task
OV/RVM SV/VMM
shadow integer i; int i;
for (i=0; i<MAX_NUM_PORTS;i++) { for (i=0; i<`MAX_NUM_PORTS;i++) begin
fork fork
{ automatic int local_copy_of_i = i;
my_monitor{i); begin
} my_monitor{local_copy_of_i);
join none end
} join_none
end
OV/RVM SV/VMM
class pcie_tlp { class pcie_tlp;
rand int payload_len; rand int payload_len;
rand bit [31:0] payload [] dynamic_size payload_len; rand bit [31:0] payload [];
… …
} constraint payload_size {
payload.size == payload_len;
}
}
endclass : pcie_tlp
4.3.7 Interfaces
Of all manual changes, interfaces require the most significant changes when converting from OV
to SV. OV uses interfaces, ports and binds to connect to DUT which has been replaced with
flexible interfaces in SV. Although most changes here are manual, the script does help out
somewhat when converting the interface from OV to SV as shown below. In SV, one needs to
instantiate the interface at the top level module and pass the interface instance reference both to
the DUT and testbench. There is no longer a need to use dynamic binding using the ‘$’ sign
(e.g., my_intf.$my_signal). Instead, one may refer to all signals in the same way one refers to
variables inside a class using the object reference (e.g., my_intf.my_signal).
OV/RVM SV/VMM
interface my_intf { interface my_port;
input clk CLOCK hdl_node "top.clk"; logic clk;
input data_valid IN_EDGE IN_SKEW hdl_node "top.dut.data_valid"; logic data_valid;
output [31:0]data OUT_EDGE OUT_SKEW hdl_node "top.dut.data[31:0]"; logic [31:0] data;
// ... // ...
OV/RVM SV/VMM
Program test { program automatic test;
… …
string log_format = "[%t] %N (%T): %M"; intial begin
string log_prefix = " "; rvm_log log = new("Test.vr","program");
rvm_log log = new("Test.vr","program"); my_log_format fmt = new();
… //pass class object extending from vmm_log_fomat
//directly pass the format and prefix string log.set_format(fmt);
log.format(string fmt, string prefix); …
… end
} endprogram: test
--------------------------------------------------------------------
class my_log_format extends vmm_log_format;
virtual function string format_msg(string name,
string instance,
string msg_type,
string severity,
ref string lines[$]);
$sformat(format_msg, "[%0t] %s (%s): ",
$time, name, msg_type);
foreach (lines [l] )
$sformat(format_msg, "%s %s", format_msg, lines[l]);
endfunction : format_msg
endclass : my_log_format
Array initializations
OV supports initializing only a portion of the declared array. In SV, array initializations must
exactly match the declared array size.
Semaphores
OV uses static integers with alloc(), semaphore_get() and semaphore_put() methods to create
and use semaphores. SV, however, contains a built-in ‘sempahore’ class that provides get() and
put() methods that support mutual exclusion. Please refer to the appendix for an example.
String methods
OV has much richer capabilities to process strings than SV. In OV, for example, we made heavy
use of the function string method match() to perform regular expression matching and backref()
to return matched patterns. SV has no such built-in equivalent functions. VMM supports similar
functionality in SV using DPI and these functions are called vmm_str_match() and
vmm_str_backref(). The regular expressions syntax between these string methods is slightly
RVM callbacks
RVM uses register_callback(), shown below, to register any instances of callbacks extensions.
This method has been deprecated in favor of the append_callback() and prepend_callback()
methods. In converting from register_callback() to append_callback() one needs only to remove
the second argument in register_callback(), which is no longer needed. The declarations for
RVM and VMM are shown below:
RVM: virtual task register_callback(rvm_log_callbacks cb, bit prepend=0);
The list of input OV files may contain wildcards, as in the following example:
The script will automatically parse the input OV files, and generate the corresponding SV files.
It is advisable to convert source and header files at the same time so that the script will be aware
of all macro names defined in header files Currently, the script is able to detect and/or fix over
100 constructs. A complete list is located in the appendix. For constructs that the script can
detect, but are too complex to fix, a <FIXME> string is added to the output SV code. After the
conversion is complete, the user can easily search for this string and implement the manual
changes as needed.
The script automatically displays a detailed list of the issues it has detected and/or fixed, and
how many such instances it has encountered. This display is generated per-file (assuming
multiple files are processed) as well as on an overall basis. A sample output from our test case is
shown below.
Of particular interest were the overall results that we received from the script for our own test
case. Specifically, the script process 24,226 lines of source code across 25 files. The script
automatically modified 10,009 lines, or ~41.3% of the source code, and the entire conversion
was performed in 12 seconds!
6. Potential Pitfalls
In this section, we will describe some of the pitfalls which we stumbled upon during the
conversion process with our example test case, as well as some of the work-around solutions we
found.
OV/RVM SV/VMM
In SV, argument C will imply type ‘ref’ as the previous argument B is defined as ‘ref’.
Additionally, arguments B and C will be implicitly determined to be inputs, as the first argument
A is defined as an input. To avoid any unintended errors in your SV code it is recommended to
manually add the direction (input, output, or ref) to all arguments in all tasks and functions.
Reserved keywords
The list of reserved keywords in SV is not identical to OV. We encountered one such example
with a variable we had declared ‘int type’. In OV, this was fine, but in SV this results in a
compile error. We simply modified our variable name to be ‘int pkt_type’, and were able to
work around the issue.
7. Conclusions
There is long-term value on converting an existing testbench from OV/RVM to SV/VMM. In
the course of this exercise, we were amazed at the sheer number of changes required to convert
Data types
Enumerated types enum bool (FALSE, typedef enum {FALSE, Y Y
TURE}; TRUE} bool;
Integer integer (2-state and 'bX) integer (4-state) Script converts Y Y
int (2-state) integer -> int
cast_assign cast_assign(dest, to); $cast(dest, to); Y Y
array initialization integer src[5] = {0, 1, 2, int src[5] = '{0, 1, 2, 3, N N
3, 4}; 4};
multi-dimensional integer src[2][3] = {{0, 1, integer src[2][3] = '{'{0, N N
array initialization 2}, {4, 5, 6}}; 1, 2}, '{4, 5, 6}};
associative array <data_type> <data_type> Y Y
assoc_array[]; assoc_array[*];
dynamic array <data_type> <data_type> Y Y
dynamic_array[*]; dynamic_array[];
Procedural block
begin-end { begin Y Y
…….. …..
} end
class class Cxyz { class Cxyz; Y Y
… …
} endclass:Cxyz
task task txyz (arg) { task txyz (arg); Y Y
… …
} endtask: txyz;
function function fxyz (args) { function fxyz (args); Y Y
… …
} endfunction: fxyz;
program program pxyz { program automatic Y Y
… pxyz;
} …
endprogram: pxyz;
Selection Statements
If if (cond) { if (cond) begin Y Y
… …
} end
else if (cond1) { else if (cond1) begin
… …
} end
else { else begin
… …
} end
Loop Statements
for for (i=0; i<X; i++) { for (int i=0; i<X;i++) Y Y
… begin
} …
end
while while (condition) { while (condition) begin Y Y
… …
} end
foreach (single foreach(src, i) { foreach(src[i]) begin Y Y
dimension) … …
} end
1:10:/20, [1:10]:/20,
1:env.pcie_cfg.cfg_env. [1:env.pcie_cfg.cfg_env
mps/4:/30, .mps/4]:/30,
y:z:/40}; [y:z]:/40};
} }
Random Number System Functions
Random random() $random() Y Y
Srandom Srandom $srandom() Y Y
Urandom urandom() $urandom() Y Y
rand48 rand48() $rand48() Y Y